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 com.android.media;
     18 
     19 import static android.media.SessionCommand2.COMMAND_CODE_CUSTOM;
     20 import static android.media.SessionToken2.TYPE_LIBRARY_SERVICE;
     21 import static android.media.SessionToken2.TYPE_SESSION;
     22 import static android.media.SessionToken2.TYPE_SESSION_SERVICE;
     23 
     24 import android.annotation.NonNull;
     25 import android.annotation.Nullable;
     26 import android.app.PendingIntent;
     27 import android.content.Context;
     28 import android.content.Intent;
     29 import android.content.pm.PackageManager;
     30 import android.content.pm.ResolveInfo;
     31 import android.media.AudioAttributes;
     32 import android.media.AudioFocusRequest;
     33 import android.media.AudioManager;
     34 import android.media.DataSourceDesc;
     35 import android.media.MediaController2;
     36 import android.media.MediaController2.PlaybackInfo;
     37 import android.media.MediaItem2;
     38 import android.media.MediaLibraryService2;
     39 import android.media.MediaMetadata2;
     40 import android.media.MediaPlayerBase;
     41 import android.media.MediaPlayerBase.PlayerEventCallback;
     42 import android.media.MediaPlayerBase.PlayerState;
     43 import android.media.MediaPlaylistAgent;
     44 import android.media.MediaPlaylistAgent.PlaylistEventCallback;
     45 import android.media.MediaSession2;
     46 import android.media.MediaSession2.Builder;
     47 import android.media.SessionCommand2;
     48 import android.media.MediaSession2.CommandButton;
     49 import android.media.SessionCommandGroup2;
     50 import android.media.MediaSession2.ControllerInfo;
     51 import android.media.MediaSession2.OnDataSourceMissingHelper;
     52 import android.media.MediaSession2.SessionCallback;
     53 import android.media.MediaSessionService2;
     54 import android.media.SessionToken2;
     55 import android.media.VolumeProvider2;
     56 import android.media.session.MediaSessionManager;
     57 import android.media.update.MediaSession2Provider;
     58 import android.os.Bundle;
     59 import android.os.IBinder;
     60 import android.os.Parcelable;
     61 import android.os.Process;
     62 import android.os.ResultReceiver;
     63 import android.support.annotation.GuardedBy;
     64 import android.text.TextUtils;
     65 import android.util.Log;
     66 
     67 import java.lang.ref.WeakReference;
     68 import java.lang.reflect.Field;
     69 import java.util.ArrayList;
     70 import java.util.Collections;
     71 import java.util.HashSet;
     72 import java.util.List;
     73 import java.util.NoSuchElementException;
     74 import java.util.Set;
     75 import java.util.concurrent.Executor;
     76 
     77 public class MediaSession2Impl implements MediaSession2Provider {
     78     private static final String TAG = "MediaSession2";
     79     private static final boolean DEBUG = true;//Log.isLoggable(TAG, Log.DEBUG);
     80 
     81     private final Object mLock = new Object();
     82 
     83     private final MediaSession2 mInstance;
     84     private final Context mContext;
     85     private final String mId;
     86     private final Executor mCallbackExecutor;
     87     private final SessionCallback mCallback;
     88     private final MediaSession2Stub mSessionStub;
     89     private final SessionToken2 mSessionToken;
     90     private final AudioManager mAudioManager;
     91     private final PendingIntent mSessionActivity;
     92     private final PlayerEventCallback mPlayerEventCallback;
     93     private final PlaylistEventCallback mPlaylistEventCallback;
     94 
     95     // mPlayer is set to null when the session is closed, and we shouldn't throw an exception
     96     // nor leave log always for using mPlayer when it's null. Here's the reason.
     97     // When a MediaSession2 is closed, there could be a pended operation in the session callback
     98     // executor that may want to access the player. Here's the sample code snippet for that.
     99     //
    100     //   public void onFoo() {
    101     //     if (mPlayer == null) return; // first check
    102     //     mSessionCallbackExecutor.executor(() -> {
    103     //       // Error. Session may be closed and mPlayer can be null here.
    104     //       mPlayer.foo();
    105     //     });
    106     //   }
    107     //
    108     // By adding protective code, we can also protect APIs from being called after the close()
    109     //
    110     // TODO(jaewan): Should we put volatile here?
    111     @GuardedBy("mLock")
    112     private MediaPlayerBase mPlayer;
    113     @GuardedBy("mLock")
    114     private MediaPlaylistAgent mPlaylistAgent;
    115     @GuardedBy("mLock")
    116     private SessionPlaylistAgent mSessionPlaylistAgent;
    117     @GuardedBy("mLock")
    118     private VolumeProvider2 mVolumeProvider;
    119     @GuardedBy("mLock")
    120     private PlaybackInfo mPlaybackInfo;
    121     @GuardedBy("mLock")
    122     private OnDataSourceMissingHelper mDsmHelper;
    123 
    124     /**
    125      * Can be only called by the {@link Builder#build()}.
    126      * @param context
    127      * @param player
    128      * @param id
    129      * @param playlistAgent
    130      * @param volumeProvider
    131      * @param sessionActivity
    132      * @param callbackExecutor
    133      * @param callback
    134      */
    135     public MediaSession2Impl(Context context, MediaPlayerBase player, String id,
    136             MediaPlaylistAgent playlistAgent, VolumeProvider2 volumeProvider,
    137             PendingIntent sessionActivity,
    138             Executor callbackExecutor, SessionCallback callback) {
    139         // TODO(jaewan): Keep other params.
    140         mInstance = createInstance();
    141 
    142         // Argument checks are done by builder already.
    143         // Initialize finals first.
    144         mContext = context;
    145         mId = id;
    146         mCallback = callback;
    147         mCallbackExecutor = callbackExecutor;
    148         mSessionActivity = sessionActivity;
    149         mSessionStub = new MediaSession2Stub(this);
    150         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
    151         mPlayerEventCallback = new MyPlayerEventCallback(this);
    152         mPlaylistEventCallback = new MyPlaylistEventCallback(this);
    153 
    154         // Infer type from the id and package name.
    155         String libraryService = getServiceName(context, MediaLibraryService2.SERVICE_INTERFACE, id);
    156         String sessionService = getServiceName(context, MediaSessionService2.SERVICE_INTERFACE, id);
    157         if (sessionService != null && libraryService != null) {
    158             throw new IllegalArgumentException("Ambiguous session type. Multiple"
    159                     + " session services define the same id=" + id);
    160         } else if (libraryService != null) {
    161             mSessionToken = new SessionToken2Impl(Process.myUid(), TYPE_LIBRARY_SERVICE,
    162                     mContext.getPackageName(), libraryService, id, mSessionStub).getInstance();
    163         } else if (sessionService != null) {
    164             mSessionToken = new SessionToken2Impl(Process.myUid(), TYPE_SESSION_SERVICE,
    165                     mContext.getPackageName(), sessionService, id, mSessionStub).getInstance();
    166         } else {
    167             mSessionToken = new SessionToken2Impl(Process.myUid(), TYPE_SESSION,
    168                     mContext.getPackageName(), null, id, mSessionStub).getInstance();
    169         }
    170 
    171         updatePlayer(player, playlistAgent, volumeProvider);
    172 
    173         // Ask server for the sanity check, and starts
    174         // Sanity check for making session ID unique 'per package' cannot be done in here.
    175         // Server can only know if the package has another process and has another session with the
    176         // same id. Note that 'ID is unique per package' is important for controller to distinguish
    177         // a session in another package.
    178         MediaSessionManager manager =
    179                 (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
    180         if (!manager.createSession2(mSessionToken)) {
    181             throw new IllegalStateException("Session with the same id is already used by"
    182                     + " another process. Use MediaController2 instead.");
    183         }
    184     }
    185 
    186     MediaSession2 createInstance() {
    187         return new MediaSession2(this);
    188     }
    189 
    190     private static String getServiceName(Context context, String serviceAction, String id) {
    191         PackageManager manager = context.getPackageManager();
    192         Intent serviceIntent = new Intent(serviceAction);
    193         serviceIntent.setPackage(context.getPackageName());
    194         List<ResolveInfo> services = manager.queryIntentServices(serviceIntent,
    195                 PackageManager.GET_META_DATA);
    196         String serviceName = null;
    197         if (services != null) {
    198             for (int i = 0; i < services.size(); i++) {
    199                 String serviceId = SessionToken2Impl.getSessionId(services.get(i));
    200                 if (serviceId != null && TextUtils.equals(id, serviceId)) {
    201                     if (services.get(i).serviceInfo == null) {
    202                         continue;
    203                     }
    204                     if (serviceName != null) {
    205                         throw new IllegalArgumentException("Ambiguous session type. Multiple"
    206                                 + " session services define the same id=" + id);
    207                     }
    208                     serviceName = services.get(i).serviceInfo.name;
    209                 }
    210             }
    211         }
    212         return serviceName;
    213     }
    214 
    215     @Override
    216     public void updatePlayer_impl(@NonNull MediaPlayerBase player, MediaPlaylistAgent playlistAgent,
    217             VolumeProvider2 volumeProvider) throws IllegalArgumentException {
    218         ensureCallingThread();
    219         if (player == null) {
    220             throw new IllegalArgumentException("player shouldn't be null");
    221         }
    222         updatePlayer(player, playlistAgent, volumeProvider);
    223     }
    224 
    225     private void updatePlayer(MediaPlayerBase player, MediaPlaylistAgent agent,
    226             VolumeProvider2 volumeProvider) {
    227         final MediaPlayerBase oldPlayer;
    228         final MediaPlaylistAgent oldAgent;
    229         final PlaybackInfo info = createPlaybackInfo(volumeProvider, player.getAudioAttributes());
    230         synchronized (mLock) {
    231             oldPlayer = mPlayer;
    232             oldAgent = mPlaylistAgent;
    233             mPlayer = player;
    234             if (agent == null) {
    235                 mSessionPlaylistAgent = new SessionPlaylistAgent(this, mPlayer);
    236                 if (mDsmHelper != null) {
    237                     mSessionPlaylistAgent.setOnDataSourceMissingHelper(mDsmHelper);
    238                 }
    239                 agent = mSessionPlaylistAgent;
    240             }
    241             mPlaylistAgent = agent;
    242             mVolumeProvider = volumeProvider;
    243             mPlaybackInfo = info;
    244         }
    245         if (player != oldPlayer) {
    246             player.registerPlayerEventCallback(mCallbackExecutor, mPlayerEventCallback);
    247             if (oldPlayer != null) {
    248                 // Warning: Poorly implement player may ignore this
    249                 oldPlayer.unregisterPlayerEventCallback(mPlayerEventCallback);
    250             }
    251         }
    252         if (agent != oldAgent) {
    253             agent.registerPlaylistEventCallback(mCallbackExecutor, mPlaylistEventCallback);
    254             if (oldAgent != null) {
    255                 // Warning: Poorly implement player may ignore this
    256                 oldAgent.unregisterPlaylistEventCallback(mPlaylistEventCallback);
    257             }
    258         }
    259 
    260         if (oldPlayer != null) {
    261             mSessionStub.notifyPlaybackInfoChanged(info);
    262             notifyPlayerUpdatedNotLocked(oldPlayer);
    263         }
    264         // TODO(jaewan): Repeat the same thing for the playlist agent.
    265     }
    266 
    267     private PlaybackInfo createPlaybackInfo(VolumeProvider2 volumeProvider, AudioAttributes attrs) {
    268         PlaybackInfo info;
    269         if (volumeProvider == null) {
    270             int stream;
    271             if (attrs == null) {
    272                 stream = AudioManager.STREAM_MUSIC;
    273             } else {
    274                 stream = attrs.getVolumeControlStream();
    275                 if (stream == AudioManager.USE_DEFAULT_STREAM_TYPE) {
    276                     // It may happen if the AudioAttributes doesn't have usage.
    277                     // Change it to the STREAM_MUSIC because it's not supported by audio manager
    278                     // for querying volume level.
    279                     stream = AudioManager.STREAM_MUSIC;
    280                 }
    281             }
    282             info = MediaController2Impl.PlaybackInfoImpl.createPlaybackInfo(
    283                     PlaybackInfo.PLAYBACK_TYPE_LOCAL,
    284                     attrs,
    285                     mAudioManager.isVolumeFixed()
    286                             ? VolumeProvider2.VOLUME_CONTROL_FIXED
    287                             : VolumeProvider2.VOLUME_CONTROL_ABSOLUTE,
    288                     mAudioManager.getStreamMaxVolume(stream),
    289                     mAudioManager.getStreamVolume(stream));
    290         } else {
    291             info = MediaController2Impl.PlaybackInfoImpl.createPlaybackInfo(
    292                     PlaybackInfo.PLAYBACK_TYPE_REMOTE /* ControlType */,
    293                     attrs,
    294                     volumeProvider.getControlType(),
    295                     volumeProvider.getMaxVolume(),
    296                     volumeProvider.getCurrentVolume());
    297         }
    298         return info;
    299     }
    300 
    301     @Override
    302     public void close_impl() {
    303         // Stop system service from listening this session first.
    304         MediaSessionManager manager =
    305                 (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
    306         manager.destroySession2(mSessionToken);
    307 
    308         if (mSessionStub != null) {
    309             if (DEBUG) {
    310                 Log.d(TAG, "session is now unavailable, id=" + mId);
    311             }
    312             // Invalidate previously published session stub.
    313             mSessionStub.destroyNotLocked();
    314         }
    315         final MediaPlayerBase player;
    316         final MediaPlaylistAgent agent;
    317         synchronized (mLock) {
    318             player = mPlayer;
    319             mPlayer = null;
    320             agent = mPlaylistAgent;
    321             mPlaylistAgent = null;
    322             mSessionPlaylistAgent = null;
    323         }
    324         if (player != null) {
    325             player.unregisterPlayerEventCallback(mPlayerEventCallback);
    326         }
    327         if (agent != null) {
    328             agent.unregisterPlaylistEventCallback(mPlaylistEventCallback);
    329         }
    330     }
    331 
    332     @Override
    333     public MediaPlayerBase getPlayer_impl() {
    334         return getPlayer();
    335     }
    336 
    337     @Override
    338     public MediaPlaylistAgent getPlaylistAgent_impl() {
    339         return mPlaylistAgent;
    340     }
    341 
    342     @Override
    343     public VolumeProvider2 getVolumeProvider_impl() {
    344         return mVolumeProvider;
    345     }
    346 
    347     @Override
    348     public SessionToken2 getToken_impl() {
    349         return mSessionToken;
    350     }
    351 
    352     @Override
    353     public List<ControllerInfo> getConnectedControllers_impl() {
    354         return mSessionStub.getControllers();
    355     }
    356 
    357     @Override
    358     public void setAudioFocusRequest_impl(AudioFocusRequest afr) {
    359         // implement
    360     }
    361 
    362     @Override
    363     public void play_impl() {
    364         ensureCallingThread();
    365         final MediaPlayerBase player = mPlayer;
    366         if (player != null) {
    367             player.play();
    368         } else if (DEBUG) {
    369             Log.d(TAG, "API calls after the close()", new IllegalStateException());
    370         }
    371     }
    372 
    373     @Override
    374     public void pause_impl() {
    375         ensureCallingThread();
    376         final MediaPlayerBase player = mPlayer;
    377         if (player != null) {
    378             player.pause();
    379         } else if (DEBUG) {
    380             Log.d(TAG, "API calls after the close()", new IllegalStateException());
    381         }
    382     }
    383 
    384     @Override
    385     public void stop_impl() {
    386         ensureCallingThread();
    387         final MediaPlayerBase player = mPlayer;
    388         if (player != null) {
    389             player.reset();
    390         } else if (DEBUG) {
    391             Log.d(TAG, "API calls after the close()", new IllegalStateException());
    392         }
    393     }
    394 
    395     @Override
    396     public void skipToPlaylistItem_impl(@NonNull MediaItem2 item) {
    397         if (item == null) {
    398             throw new IllegalArgumentException("item shouldn't be null");
    399         }
    400         final MediaPlaylistAgent agent = mPlaylistAgent;
    401         if (agent != null) {
    402             agent.skipToPlaylistItem(item);
    403         } else if (DEBUG) {
    404             Log.d(TAG, "API calls after the close()", new IllegalStateException());
    405         }
    406     }
    407 
    408     @Override
    409     public void skipToPreviousItem_impl() {
    410         final MediaPlaylistAgent agent = mPlaylistAgent;
    411         if (agent != null) {
    412             agent.skipToPreviousItem();
    413         } else if (DEBUG) {
    414             Log.d(TAG, "API calls after the close()", new IllegalStateException());
    415         }
    416     }
    417 
    418     @Override
    419     public void skipToNextItem_impl() {
    420         final MediaPlaylistAgent agent = mPlaylistAgent;
    421         if (agent != null) {
    422             agent.skipToNextItem();
    423         } else if (DEBUG) {
    424             Log.d(TAG, "API calls after the close()", new IllegalStateException());
    425         }
    426     }
    427 
    428     @Override
    429     public void setCustomLayout_impl(@NonNull ControllerInfo controller,
    430             @NonNull List<CommandButton> layout) {
    431         ensureCallingThread();
    432         if (controller == null) {
    433             throw new IllegalArgumentException("controller shouldn't be null");
    434         }
    435         if (layout == null) {
    436             throw new IllegalArgumentException("layout shouldn't be null");
    437         }
    438         mSessionStub.notifyCustomLayoutNotLocked(controller, layout);
    439     }
    440 
    441     //////////////////////////////////////////////////////////////////////////////////////
    442     // TODO(jaewan): Implement follows
    443     //////////////////////////////////////////////////////////////////////////////////////
    444 
    445     @Override
    446     public void setAllowedCommands_impl(@NonNull ControllerInfo controller,
    447             @NonNull SessionCommandGroup2 commands) {
    448         if (controller == null) {
    449             throw new IllegalArgumentException("controller shouldn't be null");
    450         }
    451         if (commands == null) {
    452             throw new IllegalArgumentException("commands shouldn't be null");
    453         }
    454         mSessionStub.setAllowedCommands(controller, commands);
    455     }
    456 
    457     @Override
    458     public void sendCustomCommand_impl(@NonNull ControllerInfo controller,
    459             @NonNull SessionCommand2 command, Bundle args, ResultReceiver receiver) {
    460         if (controller == null) {
    461             throw new IllegalArgumentException("controller shouldn't be null");
    462         }
    463         if (command == null) {
    464             throw new IllegalArgumentException("command shouldn't be null");
    465         }
    466         mSessionStub.sendCustomCommand(controller, command, args, receiver);
    467     }
    468 
    469     @Override
    470     public void sendCustomCommand_impl(@NonNull SessionCommand2 command, Bundle args) {
    471         if (command == null) {
    472             throw new IllegalArgumentException("command shouldn't be null");
    473         }
    474         mSessionStub.sendCustomCommand(command, args);
    475     }
    476 
    477     @Override
    478     public void setPlaylist_impl(@NonNull List<MediaItem2> list, MediaMetadata2 metadata) {
    479         if (list == null) {
    480             throw new IllegalArgumentException("list shouldn't be null");
    481         }
    482         ensureCallingThread();
    483         final MediaPlaylistAgent agent = mPlaylistAgent;
    484         if (agent != null) {
    485             agent.setPlaylist(list, metadata);
    486         } else if (DEBUG) {
    487             Log.d(TAG, "API calls after the close()", new IllegalStateException());
    488         }
    489     }
    490 
    491     @Override
    492     public void updatePlaylistMetadata_impl(MediaMetadata2 metadata) {
    493         final MediaPlaylistAgent agent = mPlaylistAgent;
    494         if (agent != null) {
    495             agent.updatePlaylistMetadata(metadata);
    496         } else if (DEBUG) {
    497             Log.d(TAG, "API calls after the close()", new IllegalStateException());
    498         }
    499     }
    500 
    501     @Override
    502     public void addPlaylistItem_impl(int index, @NonNull MediaItem2 item) {
    503         if (index < 0) {
    504             throw new IllegalArgumentException("index shouldn't be negative");
    505         }
    506         if (item == null) {
    507             throw new IllegalArgumentException("item shouldn't be null");
    508         }
    509         final MediaPlaylistAgent agent = mPlaylistAgent;
    510         if (agent != null) {
    511             agent.addPlaylistItem(index, item);
    512         } else if (DEBUG) {
    513             Log.d(TAG, "API calls after the close()", new IllegalStateException());
    514         }
    515     }
    516 
    517     @Override
    518     public void removePlaylistItem_impl(@NonNull MediaItem2 item) {
    519         if (item == null) {
    520             throw new IllegalArgumentException("item shouldn't be null");
    521         }
    522         final MediaPlaylistAgent agent = mPlaylistAgent;
    523         if (agent != null) {
    524             agent.removePlaylistItem(item);
    525         } else if (DEBUG) {
    526             Log.d(TAG, "API calls after the close()", new IllegalStateException());
    527         }
    528     }
    529 
    530     @Override
    531     public void replacePlaylistItem_impl(int index, @NonNull MediaItem2 item) {
    532         if (index < 0) {
    533             throw new IllegalArgumentException("index shouldn't be negative");
    534         }
    535         if (item == null) {
    536             throw new IllegalArgumentException("item shouldn't be null");
    537         }
    538         final MediaPlaylistAgent agent = mPlaylistAgent;
    539         if (agent != null) {
    540             agent.replacePlaylistItem(index, item);
    541         } else if (DEBUG) {
    542             Log.d(TAG, "API calls after the close()", new IllegalStateException());
    543         }
    544     }
    545 
    546     @Override
    547     public List<MediaItem2> getPlaylist_impl() {
    548         final MediaPlaylistAgent agent = mPlaylistAgent;
    549         if (agent != null) {
    550             return agent.getPlaylist();
    551         } else if (DEBUG) {
    552             Log.d(TAG, "API calls after the close()", new IllegalStateException());
    553         }
    554         return null;
    555     }
    556 
    557     @Override
    558     public MediaMetadata2 getPlaylistMetadata_impl() {
    559         final MediaPlaylistAgent agent = mPlaylistAgent;
    560         if (agent != null) {
    561             return agent.getPlaylistMetadata();
    562         } else if (DEBUG) {
    563             Log.d(TAG, "API calls after the close()", new IllegalStateException());
    564         }
    565         return null;
    566     }
    567 
    568     @Override
    569     public MediaItem2 getCurrentPlaylistItem_impl() {
    570         // TODO(jaewan): Implement
    571         return null;
    572     }
    573 
    574     @Override
    575     public int getRepeatMode_impl() {
    576         final MediaPlaylistAgent agent = mPlaylistAgent;
    577         if (agent != null) {
    578             return agent.getRepeatMode();
    579         } else if (DEBUG) {
    580             Log.d(TAG, "API calls after the close()", new IllegalStateException());
    581         }
    582         return MediaPlaylistAgent.REPEAT_MODE_NONE;
    583     }
    584 
    585     @Override
    586     public void setRepeatMode_impl(int repeatMode) {
    587         final MediaPlaylistAgent agent = mPlaylistAgent;
    588         if (agent != null) {
    589             agent.setRepeatMode(repeatMode);
    590         } else if (DEBUG) {
    591             Log.d(TAG, "API calls after the close()", new IllegalStateException());
    592         }
    593     }
    594 
    595     @Override
    596     public int getShuffleMode_impl() {
    597         final MediaPlaylistAgent agent = mPlaylistAgent;
    598         if (agent != null) {
    599             return agent.getShuffleMode();
    600         } else if (DEBUG) {
    601             Log.d(TAG, "API calls after the close()", new IllegalStateException());
    602         }
    603         return MediaPlaylistAgent.SHUFFLE_MODE_NONE;
    604     }
    605 
    606     @Override
    607     public void setShuffleMode_impl(int shuffleMode) {
    608         final MediaPlaylistAgent agent = mPlaylistAgent;
    609         if (agent != null) {
    610             agent.setShuffleMode(shuffleMode);
    611         } else if (DEBUG) {
    612             Log.d(TAG, "API calls after the close()", new IllegalStateException());
    613         }
    614     }
    615 
    616     @Override
    617     public void prepare_impl() {
    618         ensureCallingThread();
    619         final MediaPlayerBase player = mPlayer;
    620         if (player != null) {
    621             player.prepare();
    622         } else if (DEBUG) {
    623             Log.d(TAG, "API calls after the close()", new IllegalStateException());
    624         }
    625     }
    626 
    627     @Override
    628     public void seekTo_impl(long pos) {
    629         ensureCallingThread();
    630         final MediaPlayerBase player = mPlayer;
    631         if (player != null) {
    632             player.seekTo(pos);
    633         } else if (DEBUG) {
    634             Log.d(TAG, "API calls after the close()", new IllegalStateException());
    635         }
    636     }
    637 
    638     @Override
    639     public @PlayerState int getPlayerState_impl() {
    640         final MediaPlayerBase player = mPlayer;
    641         if (player != null) {
    642             return mPlayer.getPlayerState();
    643         } else if (DEBUG) {
    644             Log.d(TAG, "API calls after the close()", new IllegalStateException());
    645         }
    646         return MediaPlayerBase.PLAYER_STATE_ERROR;
    647     }
    648 
    649     @Override
    650     public long getCurrentPosition_impl() {
    651         final MediaPlayerBase player = mPlayer;
    652         if (player != null) {
    653             return mPlayer.getCurrentPosition();
    654         } else if (DEBUG) {
    655             Log.d(TAG, "API calls after the close()", new IllegalStateException());
    656         }
    657         return MediaPlayerBase.UNKNOWN_TIME;
    658     }
    659 
    660     @Override
    661     public long getBufferedPosition_impl() {
    662         final MediaPlayerBase player = mPlayer;
    663         if (player != null) {
    664             return mPlayer.getBufferedPosition();
    665         } else if (DEBUG) {
    666             Log.d(TAG, "API calls after the close()", new IllegalStateException());
    667         }
    668         return MediaPlayerBase.UNKNOWN_TIME;
    669     }
    670 
    671     @Override
    672     public void notifyError_impl(int errorCode, Bundle extras) {
    673         mSessionStub.notifyError(errorCode, extras);
    674     }
    675 
    676     @Override
    677     public void setOnDataSourceMissingHelper_impl(@NonNull OnDataSourceMissingHelper helper) {
    678         if (helper == null) {
    679             throw new IllegalArgumentException("helper shouldn't be null");
    680         }
    681         synchronized (mLock) {
    682             mDsmHelper = helper;
    683             if (mSessionPlaylistAgent != null) {
    684                 mSessionPlaylistAgent.setOnDataSourceMissingHelper(helper);
    685             }
    686         }
    687     }
    688 
    689     @Override
    690     public void clearOnDataSourceMissingHelper_impl() {
    691         synchronized (mLock) {
    692             mDsmHelper = null;
    693             if (mSessionPlaylistAgent != null) {
    694                 mSessionPlaylistAgent.clearOnDataSourceMissingHelper();
    695             }
    696         }
    697     }
    698 
    699     ///////////////////////////////////////////////////
    700     // Protected or private methods
    701     ///////////////////////////////////////////////////
    702 
    703     // Enforces developers to call all the methods on the initially given thread
    704     // because calls from the MediaController2 will be run on the thread.
    705     // TODO(jaewan): Should we allow calls from the multiple thread?
    706     //               I prefer this way because allowing multiple thread may case tricky issue like
    707     //               b/63446360. If the {@link #setPlayer()} with {@code null} can be called from
    708     //               another thread, transport controls can be called after that.
    709     //               That's basically the developer's mistake, but they cannot understand what's
    710     //               happening behind until we tell them so.
    711     //               If enforcing callling thread doesn't look good, we can alternatively pick
    712     //               1. Allow calls from random threads for all methods.
    713     //               2. Allow calls from random threads for all methods, except for the
    714     //                  {@link #setPlayer()}.
    715     void ensureCallingThread() {
    716         // TODO(jaewan): Uncomment or remove
    717         /*
    718         if (mHandler.getLooper() != Looper.myLooper()) {
    719             throw new IllegalStateException("Run this on the given thread");
    720         }*/
    721     }
    722 
    723     private void notifyPlaylistChangedOnExecutor(MediaPlaylistAgent playlistAgent,
    724             List<MediaItem2> list, MediaMetadata2 metadata) {
    725         if (playlistAgent != mPlaylistAgent) {
    726             // Ignore calls from the old agent.
    727             return;
    728         }
    729         mCallback.onPlaylistChanged(mInstance, playlistAgent, list, metadata);
    730         mSessionStub.notifyPlaylistChangedNotLocked(list, metadata);
    731     }
    732 
    733     private void notifyPlaylistMetadataChangedOnExecutor(MediaPlaylistAgent playlistAgent,
    734             MediaMetadata2 metadata) {
    735         if (playlistAgent != mPlaylistAgent) {
    736             // Ignore calls from the old agent.
    737             return;
    738         }
    739         mCallback.onPlaylistMetadataChanged(mInstance, playlistAgent, metadata);
    740         mSessionStub.notifyPlaylistMetadataChangedNotLocked(metadata);
    741     }
    742 
    743     private void notifyRepeatModeChangedOnExecutor(MediaPlaylistAgent playlistAgent,
    744             int repeatMode) {
    745         if (playlistAgent != mPlaylistAgent) {
    746             // Ignore calls from the old agent.
    747             return;
    748         }
    749         mCallback.onRepeatModeChanged(mInstance, playlistAgent, repeatMode);
    750         mSessionStub.notifyRepeatModeChangedNotLocked(repeatMode);
    751     }
    752 
    753     private void notifyShuffleModeChangedOnExecutor(MediaPlaylistAgent playlistAgent,
    754             int shuffleMode) {
    755         if (playlistAgent != mPlaylistAgent) {
    756             // Ignore calls from the old agent.
    757             return;
    758         }
    759         mCallback.onShuffleModeChanged(mInstance, playlistAgent, shuffleMode);
    760         mSessionStub.notifyShuffleModeChangedNotLocked(shuffleMode);
    761     }
    762 
    763     private void notifyPlayerUpdatedNotLocked(MediaPlayerBase oldPlayer) {
    764         final MediaPlayerBase player = mPlayer;
    765         // TODO(jaewan): (Can be post-P) Find better way for player.getPlayerState() //
    766         //               In theory, Session.getXXX() may not be the same as Player.getXXX()
    767         //               and we should notify information of the session.getXXX() instead of
    768         //               player.getXXX()
    769         // Notify to controllers as well.
    770         final int state = player.getPlayerState();
    771         if (state != oldPlayer.getPlayerState()) {
    772             mSessionStub.notifyPlayerStateChangedNotLocked(state);
    773         }
    774 
    775         final long currentTimeMs = System.currentTimeMillis();
    776         final long position = player.getCurrentPosition();
    777         if (position != oldPlayer.getCurrentPosition()) {
    778             mSessionStub.notifyPositionChangedNotLocked(currentTimeMs, position);
    779         }
    780 
    781         final float speed = player.getPlaybackSpeed();
    782         if (speed != oldPlayer.getPlaybackSpeed()) {
    783             mSessionStub.notifyPlaybackSpeedChangedNotLocked(speed);
    784         }
    785 
    786         final long bufferedPosition = player.getBufferedPosition();
    787         if (bufferedPosition != oldPlayer.getBufferedPosition()) {
    788             mSessionStub.notifyBufferedPositionChangedNotLocked(bufferedPosition);
    789         }
    790     }
    791 
    792     Context getContext() {
    793         return mContext;
    794     }
    795 
    796     MediaSession2 getInstance() {
    797         return mInstance;
    798     }
    799 
    800     MediaPlayerBase getPlayer() {
    801         return mPlayer;
    802     }
    803 
    804     MediaPlaylistAgent getPlaylistAgent() {
    805         return mPlaylistAgent;
    806     }
    807 
    808     Executor getCallbackExecutor() {
    809         return mCallbackExecutor;
    810     }
    811 
    812     SessionCallback getCallback() {
    813         return mCallback;
    814     }
    815 
    816     MediaSession2Stub getSessionStub() {
    817         return mSessionStub;
    818     }
    819 
    820     VolumeProvider2 getVolumeProvider() {
    821         return mVolumeProvider;
    822     }
    823 
    824     PlaybackInfo getPlaybackInfo() {
    825         synchronized (mLock) {
    826             return mPlaybackInfo;
    827         }
    828     }
    829 
    830     PendingIntent getSessionActivity() {
    831         return mSessionActivity;
    832     }
    833 
    834     private static class MyPlayerEventCallback extends PlayerEventCallback {
    835         private final WeakReference<MediaSession2Impl> mSession;
    836 
    837         private MyPlayerEventCallback(MediaSession2Impl session) {
    838             mSession = new WeakReference<>(session);
    839         }
    840 
    841         @Override
    842         public void onCurrentDataSourceChanged(MediaPlayerBase mpb, DataSourceDesc dsd) {
    843             MediaSession2Impl session = getSession();
    844             if (session == null || dsd == null) {
    845                 return;
    846             }
    847             session.getCallbackExecutor().execute(() -> {
    848                 MediaItem2 item = getMediaItem(session, dsd);
    849                 if (item == null) {
    850                     return;
    851                 }
    852                 session.getCallback().onCurrentMediaItemChanged(session.getInstance(), mpb, item);
    853                 // TODO (jaewan): Notify controllers through appropriate callback. (b/74505936)
    854             });
    855         }
    856 
    857         @Override
    858         public void onMediaPrepared(MediaPlayerBase mpb, DataSourceDesc dsd) {
    859             MediaSession2Impl session = getSession();
    860             if (session == null || dsd == null) {
    861                 return;
    862             }
    863             session.getCallbackExecutor().execute(() -> {
    864                 MediaItem2 item = getMediaItem(session, dsd);
    865                 if (item == null) {
    866                     return;
    867                 }
    868                 session.getCallback().onMediaPrepared(session.getInstance(), mpb, item);
    869                 // TODO (jaewan): Notify controllers through appropriate callback. (b/74505936)
    870             });
    871         }
    872 
    873         @Override
    874         public void onPlayerStateChanged(MediaPlayerBase mpb, int state) {
    875             MediaSession2Impl session = getSession();
    876             if (session == null) {
    877                 return;
    878             }
    879             session.getCallbackExecutor().execute(() -> {
    880                 session.getCallback().onPlayerStateChanged(session.getInstance(), mpb, state);
    881                 session.getSessionStub().notifyPlayerStateChangedNotLocked(state);
    882             });
    883         }
    884 
    885         @Override
    886         public void onBufferingStateChanged(MediaPlayerBase mpb, DataSourceDesc dsd, int state) {
    887             MediaSession2Impl session = getSession();
    888             if (session == null || dsd == null) {
    889                 return;
    890             }
    891             session.getCallbackExecutor().execute(() -> {
    892                 MediaItem2 item = getMediaItem(session, dsd);
    893                 if (item == null) {
    894                     return;
    895                 }
    896                 session.getCallback().onBufferingStateChanged(
    897                         session.getInstance(), mpb, item, state);
    898                 // TODO (jaewan): Notify controllers through appropriate callback. (b/74505936)
    899             });
    900         }
    901 
    902         private MediaSession2Impl getSession() {
    903             final MediaSession2Impl session = mSession.get();
    904             if (session == null && DEBUG) {
    905                 Log.d(TAG, "Session is closed", new IllegalStateException());
    906             }
    907             return session;
    908         }
    909 
    910         private MediaItem2 getMediaItem(MediaSession2Impl session, DataSourceDesc dsd) {
    911             MediaPlaylistAgent agent = session.getPlaylistAgent();
    912             if (agent == null) {
    913                 if (DEBUG) {
    914                     Log.d(TAG, "Session is closed", new IllegalStateException());
    915                 }
    916                 return null;
    917             }
    918             MediaItem2 item = agent.getMediaItem(dsd);
    919             if (item == null) {
    920                 if (DEBUG) {
    921                     Log.d(TAG, "Could not find matching item for dsd=" + dsd,
    922                             new NoSuchElementException());
    923                 }
    924             }
    925             return item;
    926         }
    927     }
    928 
    929     private static class MyPlaylistEventCallback extends PlaylistEventCallback {
    930         private final WeakReference<MediaSession2Impl> mSession;
    931 
    932         private MyPlaylistEventCallback(MediaSession2Impl session) {
    933             mSession = new WeakReference<>(session);
    934         }
    935 
    936         @Override
    937         public void onPlaylistChanged(MediaPlaylistAgent playlistAgent, List<MediaItem2> list,
    938                 MediaMetadata2 metadata) {
    939             final MediaSession2Impl session = mSession.get();
    940             if (session == null) {
    941                 return;
    942             }
    943             session.notifyPlaylistChangedOnExecutor(playlistAgent, list, metadata);
    944         }
    945 
    946         @Override
    947         public void onPlaylistMetadataChanged(MediaPlaylistAgent playlistAgent,
    948                 MediaMetadata2 metadata) {
    949             final MediaSession2Impl session = mSession.get();
    950             if (session == null) {
    951                 return;
    952             }
    953             session.notifyPlaylistMetadataChangedOnExecutor(playlistAgent, metadata);
    954         }
    955 
    956         @Override
    957         public void onRepeatModeChanged(MediaPlaylistAgent playlistAgent, int repeatMode) {
    958             final MediaSession2Impl session = mSession.get();
    959             if (session == null) {
    960                 return;
    961             }
    962             session.notifyRepeatModeChangedOnExecutor(playlistAgent, repeatMode);
    963         }
    964 
    965         @Override
    966         public void onShuffleModeChanged(MediaPlaylistAgent playlistAgent, int shuffleMode) {
    967             final MediaSession2Impl session = mSession.get();
    968             if (session == null) {
    969                 return;
    970             }
    971             session.notifyShuffleModeChangedOnExecutor(playlistAgent, shuffleMode);
    972         }
    973     }
    974 
    975     public static final class CommandImpl implements CommandProvider {
    976         private static final String KEY_COMMAND_CODE
    977                 = "android.media.media_session2.command.command_code";
    978         private static final String KEY_COMMAND_CUSTOM_COMMAND
    979                 = "android.media.media_session2.command.custom_command";
    980         private static final String KEY_COMMAND_EXTRAS
    981                 = "android.media.media_session2.command.extras";
    982 
    983         private final SessionCommand2 mInstance;
    984         private final int mCommandCode;
    985         // Nonnull if it's custom command
    986         private final String mCustomCommand;
    987         private final Bundle mExtras;
    988 
    989         public CommandImpl(SessionCommand2 instance, int commandCode) {
    990             mInstance = instance;
    991             mCommandCode = commandCode;
    992             mCustomCommand = null;
    993             mExtras = null;
    994         }
    995 
    996         public CommandImpl(SessionCommand2 instance, @NonNull String action,
    997                 @Nullable Bundle extras) {
    998             if (action == null) {
    999                 throw new IllegalArgumentException("action shouldn't be null");
   1000             }
   1001             mInstance = instance;
   1002             mCommandCode = COMMAND_CODE_CUSTOM;
   1003             mCustomCommand = action;
   1004             mExtras = extras;
   1005         }
   1006 
   1007         @Override
   1008         public int getCommandCode_impl() {
   1009             return mCommandCode;
   1010         }
   1011 
   1012         @Override
   1013         public @Nullable String getCustomCommand_impl() {
   1014             return mCustomCommand;
   1015         }
   1016 
   1017         @Override
   1018         public @Nullable Bundle getExtras_impl() {
   1019             return mExtras;
   1020         }
   1021 
   1022         /**
   1023          * @return a new Bundle instance from the Command
   1024          */
   1025         @Override
   1026         public Bundle toBundle_impl() {
   1027             Bundle bundle = new Bundle();
   1028             bundle.putInt(KEY_COMMAND_CODE, mCommandCode);
   1029             bundle.putString(KEY_COMMAND_CUSTOM_COMMAND, mCustomCommand);
   1030             bundle.putBundle(KEY_COMMAND_EXTRAS, mExtras);
   1031             return bundle;
   1032         }
   1033 
   1034         /**
   1035          * @return a new Command instance from the Bundle
   1036          */
   1037         public static SessionCommand2 fromBundle_impl(@NonNull Bundle command) {
   1038             if (command == null) {
   1039                 throw new IllegalArgumentException("command shouldn't be null");
   1040             }
   1041             int code = command.getInt(KEY_COMMAND_CODE);
   1042             if (code != COMMAND_CODE_CUSTOM) {
   1043                 return new SessionCommand2(code);
   1044             } else {
   1045                 String customCommand = command.getString(KEY_COMMAND_CUSTOM_COMMAND);
   1046                 if (customCommand == null) {
   1047                     return null;
   1048                 }
   1049                 return new SessionCommand2(customCommand, command.getBundle(KEY_COMMAND_EXTRAS));
   1050             }
   1051         }
   1052 
   1053         @Override
   1054         public boolean equals_impl(Object obj) {
   1055             if (!(obj instanceof CommandImpl)) {
   1056                 return false;
   1057             }
   1058             CommandImpl other = (CommandImpl) obj;
   1059             // TODO(jaewan): Compare Commands with the generated UUID, as we're doing for the MI2.
   1060             return mCommandCode == other.mCommandCode
   1061                     && TextUtils.equals(mCustomCommand, other.mCustomCommand);
   1062         }
   1063 
   1064         @Override
   1065         public int hashCode_impl() {
   1066             final int prime = 31;
   1067             return ((mCustomCommand != null)
   1068                     ? mCustomCommand.hashCode() : 0) * prime + mCommandCode;
   1069         }
   1070     }
   1071 
   1072     /**
   1073      * Represent set of {@link SessionCommand2}.
   1074      */
   1075     public static class CommandGroupImpl implements CommandGroupProvider {
   1076         private static final String KEY_COMMANDS =
   1077                 "android.media.mediasession2.commandgroup.commands";
   1078 
   1079         // Prefix for all command codes
   1080         private static final String PREFIX_COMMAND_CODE = "COMMAND_CODE_";
   1081 
   1082         // Prefix for command codes that will be sent directly to the MediaPlayerBase
   1083         private static final String PREFIX_COMMAND_CODE_PLAYBACK = "COMMAND_CODE_PLAYBACK_";
   1084 
   1085         // Prefix for command codes that will be sent directly to the MediaPlaylistAgent
   1086         private static final String PREFIX_COMMAND_CODE_PLAYLIST = "COMMAND_CODE_PLAYLIST_";
   1087 
   1088         private Set<SessionCommand2> mCommands = new HashSet<>();
   1089         private final SessionCommandGroup2 mInstance;
   1090 
   1091         public CommandGroupImpl(SessionCommandGroup2 instance, Object other) {
   1092             mInstance = instance;
   1093             if (other != null && other instanceof CommandGroupImpl) {
   1094                 mCommands.addAll(((CommandGroupImpl) other).mCommands);
   1095             }
   1096         }
   1097 
   1098         public CommandGroupImpl() {
   1099             mInstance = new SessionCommandGroup2(this);
   1100         }
   1101 
   1102         @Override
   1103         public void addCommand_impl(@NonNull SessionCommand2 command) {
   1104             if (command == null) {
   1105                 throw new IllegalArgumentException("command shouldn't be null");
   1106             }
   1107             mCommands.add(command);
   1108         }
   1109 
   1110         @Override
   1111         public void addAllPredefinedCommands_impl() {
   1112             addCommandsWithPrefix(PREFIX_COMMAND_CODE);
   1113         }
   1114 
   1115         void addAllPlaybackCommands() {
   1116             addCommandsWithPrefix(PREFIX_COMMAND_CODE_PLAYBACK);
   1117         }
   1118 
   1119         void addAllPlaylistCommands() {
   1120             addCommandsWithPrefix(PREFIX_COMMAND_CODE_PLAYLIST);
   1121         }
   1122 
   1123         private void addCommandsWithPrefix(String prefix) {
   1124             // TODO(jaewan): (Can be post-P): Don't use reflection for this purpose.
   1125             final Field[] fields = MediaSession2.class.getFields();
   1126             if (fields != null) {
   1127                 for (int i = 0; i < fields.length; i++) {
   1128                     if (fields[i].getName().startsWith(prefix)) {
   1129                         try {
   1130                             mCommands.add(new SessionCommand2(fields[i].getInt(null)));
   1131                         } catch (IllegalAccessException e) {
   1132                             Log.w(TAG, "Unexpected " + fields[i] + " in MediaSession2");
   1133                         }
   1134                     }
   1135                 }
   1136             }
   1137         }
   1138 
   1139         @Override
   1140         public void removeCommand_impl(@NonNull SessionCommand2 command) {
   1141             if (command == null) {
   1142                 throw new IllegalArgumentException("command shouldn't be null");
   1143             }
   1144             mCommands.remove(command);
   1145         }
   1146 
   1147         @Override
   1148         public boolean hasCommand_impl(@NonNull SessionCommand2 command) {
   1149             if (command == null) {
   1150                 throw new IllegalArgumentException("command shouldn't be null");
   1151             }
   1152             return mCommands.contains(command);
   1153         }
   1154 
   1155         @Override
   1156         public boolean hasCommand_impl(int code) {
   1157             if (code == COMMAND_CODE_CUSTOM) {
   1158                 throw new IllegalArgumentException("Use hasCommand(Command) for custom command");
   1159             }
   1160             for (SessionCommand2 command : mCommands) {
   1161                 if (command.getCommandCode() == code) {
   1162                     return true;
   1163                 }
   1164             }
   1165             return false;
   1166         }
   1167 
   1168         @Override
   1169         public Set<SessionCommand2> getCommands_impl() {
   1170             return getCommands();
   1171         }
   1172 
   1173         public Set<SessionCommand2> getCommands() {
   1174             return Collections.unmodifiableSet(mCommands);
   1175         }
   1176 
   1177         /**
   1178          * @return new bundle from the CommandGroup
   1179          * @hide
   1180          */
   1181         @Override
   1182         public Bundle toBundle_impl() {
   1183             ArrayList<Bundle> list = new ArrayList<>();
   1184             for (SessionCommand2 command : mCommands) {
   1185                 list.add(command.toBundle());
   1186             }
   1187             Bundle bundle = new Bundle();
   1188             bundle.putParcelableArrayList(KEY_COMMANDS, list);
   1189             return bundle;
   1190         }
   1191 
   1192         /**
   1193          * @return new instance of CommandGroup from the bundle
   1194          * @hide
   1195          */
   1196         public static @Nullable SessionCommandGroup2 fromBundle_impl(Bundle commands) {
   1197             if (commands == null) {
   1198                 return null;
   1199             }
   1200             List<Parcelable> list = commands.getParcelableArrayList(KEY_COMMANDS);
   1201             if (list == null) {
   1202                 return null;
   1203             }
   1204             SessionCommandGroup2 commandGroup = new SessionCommandGroup2();
   1205             for (int i = 0; i < list.size(); i++) {
   1206                 Parcelable parcelable = list.get(i);
   1207                 if (!(parcelable instanceof Bundle)) {
   1208                     continue;
   1209                 }
   1210                 Bundle commandBundle = (Bundle) parcelable;
   1211                 SessionCommand2 command = SessionCommand2.fromBundle(commandBundle);
   1212                 if (command != null) {
   1213                     commandGroup.addCommand(command);
   1214                 }
   1215             }
   1216             return commandGroup;
   1217         }
   1218     }
   1219 
   1220     public static class ControllerInfoImpl implements ControllerInfoProvider {
   1221         private final ControllerInfo mInstance;
   1222         private final int mUid;
   1223         private final String mPackageName;
   1224         private final boolean mIsTrusted;
   1225         private final IMediaController2 mControllerBinder;
   1226 
   1227         public ControllerInfoImpl(Context context, ControllerInfo instance, int uid,
   1228                 int pid, @NonNull String packageName, @NonNull IMediaController2 callback) {
   1229             if (TextUtils.isEmpty(packageName)) {
   1230                 throw new IllegalArgumentException("packageName shouldn't be empty");
   1231             }
   1232             if (callback == null) {
   1233                 throw new IllegalArgumentException("callback shouldn't be null");
   1234             }
   1235 
   1236             mInstance = instance;
   1237             mUid = uid;
   1238             mPackageName = packageName;
   1239             mControllerBinder = callback;
   1240             MediaSessionManager manager =
   1241                   (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);
   1242             // Ask server whether the controller is trusted.
   1243             // App cannot know this because apps cannot query enabled notification listener for
   1244             // another package, but system server can do.
   1245             mIsTrusted = manager.isTrustedForMediaControl(
   1246                     new MediaSessionManager.RemoteUserInfo(packageName, pid, uid));
   1247         }
   1248 
   1249         @Override
   1250         public String getPackageName_impl() {
   1251             return mPackageName;
   1252         }
   1253 
   1254         @Override
   1255         public int getUid_impl() {
   1256             return mUid;
   1257         }
   1258 
   1259         @Override
   1260         public boolean isTrusted_impl() {
   1261             return mIsTrusted;
   1262         }
   1263 
   1264         @Override
   1265         public int hashCode_impl() {
   1266             return mControllerBinder.hashCode();
   1267         }
   1268 
   1269         @Override
   1270         public boolean equals_impl(Object obj) {
   1271             if (!(obj instanceof ControllerInfo)) {
   1272                 return false;
   1273             }
   1274             return equals(((ControllerInfo) obj).getProvider());
   1275         }
   1276 
   1277         @Override
   1278         public String toString_impl() {
   1279             return "ControllerInfo {pkg=" + mPackageName + ", uid=" + mUid + ", trusted="
   1280                     + mIsTrusted + "}";
   1281         }
   1282 
   1283         @Override
   1284         public int hashCode() {
   1285             return mControllerBinder.hashCode();
   1286         }
   1287 
   1288         @Override
   1289         public boolean equals(Object obj) {
   1290             if (!(obj instanceof ControllerInfoImpl)) {
   1291                 return false;
   1292             }
   1293             ControllerInfoImpl other = (ControllerInfoImpl) obj;
   1294             return mControllerBinder.asBinder().equals(other.mControllerBinder.asBinder());
   1295         }
   1296 
   1297         ControllerInfo getInstance() {
   1298             return mInstance;
   1299         }
   1300 
   1301         IBinder getId() {
   1302             return mControllerBinder.asBinder();
   1303         }
   1304 
   1305         IMediaController2 getControllerBinder() {
   1306             return mControllerBinder;
   1307         }
   1308 
   1309         static ControllerInfoImpl from(ControllerInfo controller) {
   1310             return (ControllerInfoImpl) controller.getProvider();
   1311         }
   1312     }
   1313 
   1314     public static class CommandButtonImpl implements CommandButtonProvider {
   1315         private static final String KEY_COMMAND
   1316                 = "android.media.media_session2.command_button.command";
   1317         private static final String KEY_ICON_RES_ID
   1318                 = "android.media.media_session2.command_button.icon_res_id";
   1319         private static final String KEY_DISPLAY_NAME
   1320                 = "android.media.media_session2.command_button.display_name";
   1321         private static final String KEY_EXTRAS
   1322                 = "android.media.media_session2.command_button.extras";
   1323         private static final String KEY_ENABLED
   1324                 = "android.media.media_session2.command_button.enabled";
   1325 
   1326         private final CommandButton mInstance;
   1327         private SessionCommand2 mCommand;
   1328         private int mIconResId;
   1329         private String mDisplayName;
   1330         private Bundle mExtras;
   1331         private boolean mEnabled;
   1332 
   1333         public CommandButtonImpl(@Nullable SessionCommand2 command, int iconResId,
   1334                 @Nullable String displayName, Bundle extras, boolean enabled) {
   1335             mCommand = command;
   1336             mIconResId = iconResId;
   1337             mDisplayName = displayName;
   1338             mExtras = extras;
   1339             mEnabled = enabled;
   1340             mInstance = new CommandButton(this);
   1341         }
   1342 
   1343         @Override
   1344         public @Nullable
   1345         SessionCommand2 getCommand_impl() {
   1346             return mCommand;
   1347         }
   1348 
   1349         @Override
   1350         public int getIconResId_impl() {
   1351             return mIconResId;
   1352         }
   1353 
   1354         @Override
   1355         public @Nullable String getDisplayName_impl() {
   1356             return mDisplayName;
   1357         }
   1358 
   1359         @Override
   1360         public @Nullable Bundle getExtras_impl() {
   1361             return mExtras;
   1362         }
   1363 
   1364         @Override
   1365         public boolean isEnabled_impl() {
   1366             return mEnabled;
   1367         }
   1368 
   1369         @NonNull Bundle toBundle() {
   1370             Bundle bundle = new Bundle();
   1371             bundle.putBundle(KEY_COMMAND, mCommand.toBundle());
   1372             bundle.putInt(KEY_ICON_RES_ID, mIconResId);
   1373             bundle.putString(KEY_DISPLAY_NAME, mDisplayName);
   1374             bundle.putBundle(KEY_EXTRAS, mExtras);
   1375             bundle.putBoolean(KEY_ENABLED, mEnabled);
   1376             return bundle;
   1377         }
   1378 
   1379         static @Nullable CommandButton fromBundle(Bundle bundle) {
   1380             if (bundle == null) {
   1381                 return null;
   1382             }
   1383             CommandButton.Builder builder = new CommandButton.Builder();
   1384             builder.setCommand(SessionCommand2.fromBundle(bundle.getBundle(KEY_COMMAND)));
   1385             builder.setIconResId(bundle.getInt(KEY_ICON_RES_ID, 0));
   1386             builder.setDisplayName(bundle.getString(KEY_DISPLAY_NAME));
   1387             builder.setExtras(bundle.getBundle(KEY_EXTRAS));
   1388             builder.setEnabled(bundle.getBoolean(KEY_ENABLED));
   1389             try {
   1390                 return builder.build();
   1391             } catch (IllegalStateException e) {
   1392                 // Malformed or version mismatch. Return null for now.
   1393                 return null;
   1394             }
   1395         }
   1396 
   1397         /**
   1398          * Builder for {@link CommandButton}.
   1399          */
   1400         public static class BuilderImpl implements CommandButtonProvider.BuilderProvider {
   1401             private final CommandButton.Builder mInstance;
   1402             private SessionCommand2 mCommand;
   1403             private int mIconResId;
   1404             private String mDisplayName;
   1405             private Bundle mExtras;
   1406             private boolean mEnabled;
   1407 
   1408             public BuilderImpl(CommandButton.Builder instance) {
   1409                 mInstance = instance;
   1410                 mEnabled = true;
   1411             }
   1412 
   1413             @Override
   1414             public CommandButton.Builder setCommand_impl(SessionCommand2 command) {
   1415                 mCommand = command;
   1416                 return mInstance;
   1417             }
   1418 
   1419             @Override
   1420             public CommandButton.Builder setIconResId_impl(int resId) {
   1421                 mIconResId = resId;
   1422                 return mInstance;
   1423             }
   1424 
   1425             @Override
   1426             public CommandButton.Builder setDisplayName_impl(String displayName) {
   1427                 mDisplayName = displayName;
   1428                 return mInstance;
   1429             }
   1430 
   1431             @Override
   1432             public CommandButton.Builder setEnabled_impl(boolean enabled) {
   1433                 mEnabled = enabled;
   1434                 return mInstance;
   1435             }
   1436 
   1437             @Override
   1438             public CommandButton.Builder setExtras_impl(Bundle extras) {
   1439                 mExtras = extras;
   1440                 return mInstance;
   1441             }
   1442 
   1443             @Override
   1444             public CommandButton build_impl() {
   1445                 if (mEnabled && mCommand == null) {
   1446                     throw new IllegalStateException("Enabled button needs Command"
   1447                             + " for controller to invoke the command");
   1448                 }
   1449                 if (mCommand != null && mCommand.getCommandCode() == COMMAND_CODE_CUSTOM
   1450                         && (mIconResId == 0 || TextUtils.isEmpty(mDisplayName))) {
   1451                     throw new IllegalStateException("Custom commands needs icon and"
   1452                             + " and name to display");
   1453                 }
   1454                 return new CommandButtonImpl(mCommand, mIconResId, mDisplayName, mExtras, mEnabled)
   1455                         .mInstance;
   1456             }
   1457         }
   1458     }
   1459 
   1460     public static abstract class BuilderBaseImpl<T extends MediaSession2, C extends SessionCallback>
   1461             implements BuilderBaseProvider<T, C> {
   1462         final Context mContext;
   1463         MediaPlayerBase mPlayer;
   1464         String mId;
   1465         Executor mCallbackExecutor;
   1466         C mCallback;
   1467         MediaPlaylistAgent mPlaylistAgent;
   1468         VolumeProvider2 mVolumeProvider;
   1469         PendingIntent mSessionActivity;
   1470 
   1471         /**
   1472          * Constructor.
   1473          *
   1474          * @param context a context
   1475          * @throws IllegalArgumentException if any parameter is null, or the player is a
   1476          *      {@link MediaSession2} or {@link MediaController2}.
   1477          */
   1478         // TODO(jaewan): Also need executor
   1479         public BuilderBaseImpl(@NonNull Context context) {
   1480             if (context == null) {
   1481                 throw new IllegalArgumentException("context shouldn't be null");
   1482             }
   1483             mContext = context;
   1484             // Ensure non-null
   1485             mId = "";
   1486         }
   1487 
   1488         @Override
   1489         public void setPlayer_impl(@NonNull MediaPlayerBase player) {
   1490             if (player == null) {
   1491                 throw new IllegalArgumentException("player shouldn't be null");
   1492             }
   1493             mPlayer = player;
   1494         }
   1495 
   1496         @Override
   1497         public void setPlaylistAgent_impl(@NonNull MediaPlaylistAgent playlistAgent) {
   1498             if (playlistAgent == null) {
   1499                 throw new IllegalArgumentException("playlistAgent shouldn't be null");
   1500             }
   1501             mPlaylistAgent = playlistAgent;
   1502         }
   1503 
   1504         @Override
   1505         public void setVolumeProvider_impl(VolumeProvider2 volumeProvider) {
   1506             mVolumeProvider = volumeProvider;
   1507         }
   1508 
   1509         @Override
   1510         public void setSessionActivity_impl(PendingIntent pi) {
   1511             mSessionActivity = pi;
   1512         }
   1513 
   1514         @Override
   1515         public void setId_impl(@NonNull String id) {
   1516             if (id == null) {
   1517                 throw new IllegalArgumentException("id shouldn't be null");
   1518             }
   1519             mId = id;
   1520         }
   1521 
   1522         @Override
   1523         public void setSessionCallback_impl(@NonNull Executor executor, @NonNull C callback) {
   1524             if (executor == null) {
   1525                 throw new IllegalArgumentException("executor shouldn't be null");
   1526             }
   1527             if (callback == null) {
   1528                 throw new IllegalArgumentException("callback shouldn't be null");
   1529             }
   1530             mCallbackExecutor = executor;
   1531             mCallback = callback;
   1532         }
   1533 
   1534         @Override
   1535         public abstract T build_impl();
   1536     }
   1537 
   1538     public static class BuilderImpl extends BuilderBaseImpl<MediaSession2, SessionCallback> {
   1539         public BuilderImpl(Context context, Builder instance) {
   1540             super(context);
   1541         }
   1542 
   1543         @Override
   1544         public MediaSession2 build_impl() {
   1545             if (mCallbackExecutor == null) {
   1546                 mCallbackExecutor = mContext.getMainExecutor();
   1547             }
   1548             if (mCallback == null) {
   1549                 mCallback = new SessionCallback() {};
   1550             }
   1551             return new MediaSession2Impl(mContext, mPlayer, mId, mPlaylistAgent,
   1552                     mVolumeProvider, mSessionActivity, mCallbackExecutor, mCallback).getInstance();
   1553         }
   1554     }
   1555 }
   1556