MediaSession2Test.java revision f66f34b424baf93d223bb905184b5b6e1d086c5d
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
17package androidx.media;
18
19import static android.media.AudioAttributes.CONTENT_TYPE_MUSIC;
20
21import static androidx.media.VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE;
22import static androidx.media.VolumeProviderCompat.VOLUME_CONTROL_FIXED;
23
24import static org.junit.Assert.assertEquals;
25import static org.junit.Assert.assertFalse;
26import static org.junit.Assert.assertNotEquals;
27import static org.junit.Assert.assertNotNull;
28import static org.junit.Assert.assertNull;
29import static org.junit.Assert.assertSame;
30import static org.junit.Assert.assertTrue;
31import static org.junit.Assert.fail;
32
33import android.content.Context;
34import android.media.AudioManager;
35import android.os.Build;
36import android.os.Bundle;
37import android.os.Process;
38import android.os.ResultReceiver;
39import android.support.test.filters.SdkSuppress;
40import android.support.test.filters.SmallTest;
41import android.support.test.runner.AndroidJUnit4;
42
43import androidx.annotation.NonNull;
44import androidx.annotation.Nullable;
45import androidx.media.MediaController2.ControllerCallback;
46import androidx.media.MediaController2.PlaybackInfo;
47import androidx.media.MediaSession2.CommandButton;
48import androidx.media.MediaSession2.ControllerInfo;
49import androidx.media.MediaSession2.SessionCallback;
50
51import junit.framework.Assert;
52
53import org.junit.After;
54import org.junit.Before;
55import org.junit.Ignore;
56import org.junit.Test;
57import org.junit.runner.RunWith;
58
59import java.util.ArrayList;
60import java.util.List;
61import java.util.Set;
62import java.util.concurrent.CountDownLatch;
63import java.util.concurrent.TimeUnit;
64
65/**
66 * Tests {@link MediaSession2}.
67 */
68@SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT)
69@RunWith(AndroidJUnit4.class)
70@SmallTest
71public class MediaSession2Test extends MediaSession2TestBase {
72    private static final String TAG = "MediaSession2Test";
73
74    private MediaSession2 mSession;
75    private MockPlayer mPlayer;
76    private MockPlaylistAgent mMockAgent;
77
78    @Before
79    @Override
80    public void setUp() throws Exception {
81        super.setUp();
82        mPlayer = new MockPlayer(0);
83        mMockAgent = new MockPlaylistAgent();
84
85        mSession = new MediaSession2.Builder(mContext)
86                .setPlayer(mPlayer)
87                .setPlaylistAgent(mMockAgent)
88                .setSessionCallback(sHandlerExecutor, new SessionCallback() {
89                    @Override
90                    public SessionCommandGroup2 onConnect(MediaSession2 session,
91                            ControllerInfo controller) {
92                        if (Process.myUid() == controller.getUid()) {
93                            return super.onConnect(session, controller);
94                        }
95                        return null;
96                    }
97                }).build();
98    }
99
100    @After
101    @Override
102    public void cleanUp() throws Exception {
103        super.cleanUp();
104        mSession.close();
105    }
106
107    @Test
108    public void testBuilder() {
109        prepareLooper();
110        MediaSession2.Builder builder = new MediaSession2.Builder(mContext);
111        try {
112            builder.setPlayer(null);
113            fail("null player shouldn't be allowed");
114        } catch (IllegalArgumentException e) {
115            // expected. pass-through
116        }
117        try {
118            builder.setId(null);
119            fail("null id shouldn't be allowed");
120        } catch (IllegalArgumentException e) {
121            // expected. pass-through
122        }
123    }
124
125    @Test
126    public void testPlayerStateChange() throws Exception {
127        prepareLooper();
128        final int targetState = MediaPlayerBase.PLAYER_STATE_PLAYING;
129        final CountDownLatch latchForSessionCallback = new CountDownLatch(1);
130        sHandler.postAndSync(new Runnable() {
131            @Override
132            public void run() {
133                mSession.close();
134                mSession = new MediaSession2.Builder(mContext)
135                        .setPlayer(mPlayer)
136                        .setSessionCallback(sHandlerExecutor, new SessionCallback() {
137                            @Override
138                            public void onPlayerStateChanged(MediaSession2 session,
139                                    MediaPlayerBase player, int state) {
140                                assertEquals(targetState, state);
141                                latchForSessionCallback.countDown();
142                            }
143                        }).build();
144            }
145        });
146
147        final CountDownLatch latchForControllerCallback = new CountDownLatch(1);
148        final MediaController2 controller =
149                createController(mSession.getToken(), true, new ControllerCallback() {
150                    @Override
151                    public void onPlayerStateChanged(MediaController2 controllerOut, int state) {
152                        assertEquals(targetState, state);
153                        latchForControllerCallback.countDown();
154                    }
155                });
156
157        mPlayer.notifyPlaybackState(MediaPlayerBase.PLAYER_STATE_PLAYING);
158        assertTrue(latchForSessionCallback.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
159        assertTrue(latchForControllerCallback.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
160        assertEquals(targetState, controller.getPlayerState());
161    }
162
163    @Test
164    public void testCurrentDataSourceChanged() throws Exception {
165        prepareLooper();
166        final int listSize = 5;
167        final List<MediaItem2> list = TestUtils.createPlaylist(listSize);
168        mMockAgent.setPlaylist(list, null);
169
170        final MediaItem2 currentItem = list.get(3);
171        final CountDownLatch latchForSessionCallback = new CountDownLatch(1);
172        try (MediaSession2 session = new MediaSession2.Builder(mContext)
173                .setPlayer(mPlayer)
174                .setPlaylistAgent(mMockAgent)
175                .setId("testCurrentDataSourceChanged")
176                .setSessionCallback(sHandlerExecutor, new SessionCallback() {
177                    @Override
178                    public void onCurrentMediaItemChanged(MediaSession2 session,
179                            MediaPlayerBase player, MediaItem2 itemOut) {
180                        assertSame(currentItem, itemOut);
181                        latchForSessionCallback.countDown();
182                    }
183                }).build()) {
184
185            mPlayer.notifyCurrentDataSourceChanged(currentItem.getDataSourceDesc());
186            assertTrue(latchForSessionCallback.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
187            // TODO(jaewan): Test that controllers are also notified. (b/74505936)
188        }
189    }
190
191    @Test
192    public void testMediaPrepared() throws Exception {
193        prepareLooper();
194        final int listSize = 5;
195        final List<MediaItem2> list = TestUtils.createPlaylist(listSize);
196        mMockAgent.setPlaylist(list, null);
197
198        final MediaItem2 currentItem = list.get(3);
199
200        final CountDownLatch latchForSessionCallback = new CountDownLatch(1);
201        try (MediaSession2 session = new MediaSession2.Builder(mContext)
202                .setPlayer(mPlayer)
203                .setPlaylistAgent(mMockAgent)
204                .setId("testMediaPrepared")
205                .setSessionCallback(sHandlerExecutor, new SessionCallback() {
206                    @Override
207                    public void onMediaPrepared(MediaSession2 session, MediaPlayerBase player,
208                            MediaItem2 itemOut) {
209                        assertSame(currentItem, itemOut);
210                        latchForSessionCallback.countDown();
211                    }
212                }).build()) {
213
214            mPlayer.notifyMediaPrepared(currentItem.getDataSourceDesc());
215            assertTrue(latchForSessionCallback.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
216            // TODO(jaewan): Test that controllers are also notified. (b/74505936)
217        }
218    }
219
220    @Test
221    public void testBufferingStateChanged() throws Exception {
222        prepareLooper();
223        final int listSize = 5;
224        final List<MediaItem2> list = TestUtils.createPlaylist(listSize);
225        mMockAgent.setPlaylist(list, null);
226
227        final MediaItem2 currentItem = list.get(3);
228        final int buffState = MediaPlayerBase.BUFFERING_STATE_BUFFERING_COMPLETE;
229
230        final CountDownLatch latchForSessionCallback = new CountDownLatch(1);
231        try (MediaSession2 session = new MediaSession2.Builder(mContext)
232                .setPlayer(mPlayer)
233                .setPlaylistAgent(mMockAgent)
234                .setId("testBufferingStateChanged")
235                .setSessionCallback(sHandlerExecutor, new SessionCallback() {
236                    @Override
237                    public void onBufferingStateChanged(MediaSession2 session,
238                            MediaPlayerBase player, MediaItem2 itemOut, int stateOut) {
239                        assertSame(currentItem, itemOut);
240                        assertEquals(buffState, stateOut);
241                        latchForSessionCallback.countDown();
242                    }
243                }).build()) {
244
245            mPlayer.notifyBufferingStateChanged(currentItem.getDataSourceDesc(), buffState);
246            assertTrue(latchForSessionCallback.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
247            // TODO(jaewan): Test that controllers are also notified. (b/74505936)
248        }
249    }
250
251    @Test
252    public void testUpdatePlayer() throws Exception {
253        prepareLooper();
254        final int targetState = MediaPlayerBase.PLAYER_STATE_PLAYING;
255        final CountDownLatch latch = new CountDownLatch(1);
256        sHandler.postAndSync(new Runnable() {
257            @Override
258            public void run() {
259                mSession.close();
260                mSession = new MediaSession2.Builder(mContext).setPlayer(mPlayer)
261                        .setSessionCallback(sHandlerExecutor, new SessionCallback() {
262                            @Override
263                            public void onPlayerStateChanged(MediaSession2 session,
264                                    MediaPlayerBase player, int state) {
265                                assertEquals(targetState, state);
266                                latch.countDown();
267                            }
268                        }).build();
269            }
270        });
271
272        MockPlayer player = new MockPlayer(0);
273
274        // Test if setPlayer doesn't crash with various situations.
275        mSession.updatePlayer(mPlayer, null, null);
276        assertEquals(mPlayer, mSession.getPlayer());
277        MediaPlaylistAgent agent = mSession.getPlaylistAgent();
278        assertNotNull(agent);
279
280        mSession.updatePlayer(player, null, null);
281        assertEquals(player, mSession.getPlayer());
282        assertNotNull(mSession.getPlaylistAgent());
283        assertNotEquals(agent, mSession.getPlaylistAgent());
284
285        player.notifyPlaybackState(MediaPlayerBase.PLAYER_STATE_PLAYING);
286        assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
287    }
288
289    @Test
290    public void testSetPlayer_playbackInfo() throws Exception {
291        prepareLooper();
292        MockPlayer player = new MockPlayer(0);
293        final AudioAttributesCompat attrs = new AudioAttributesCompat.Builder()
294                .setContentType(CONTENT_TYPE_MUSIC)
295                .build();
296        player.setAudioAttributes(attrs);
297
298        final int maxVolume = 100;
299        final int currentVolume = 23;
300        final int volumeControlType = VOLUME_CONTROL_ABSOLUTE;
301        VolumeProviderCompat volumeProvider = new VolumeProviderCompat(
302                volumeControlType, maxVolume, currentVolume) { };
303
304        final CountDownLatch latch = new CountDownLatch(1);
305        final ControllerCallback callback = new ControllerCallback() {
306            @Override
307            public void onPlaybackInfoChanged(MediaController2 controller, PlaybackInfo info) {
308                Assert.assertEquals(PlaybackInfo.PLAYBACK_TYPE_REMOTE, info.getPlaybackType());
309                assertEquals(attrs, info.getAudioAttributes());
310                assertEquals(volumeControlType, info.getPlaybackType());
311                assertEquals(maxVolume, info.getMaxVolume());
312                assertEquals(currentVolume, info.getCurrentVolume());
313                latch.countDown();
314            }
315        };
316
317        mSession.updatePlayer(player, null, null);
318
319        final MediaController2 controller = createController(mSession.getToken(), true, callback);
320        PlaybackInfo info = controller.getPlaybackInfo();
321        assertNotNull(info);
322        assertEquals(PlaybackInfo.PLAYBACK_TYPE_LOCAL, info.getPlaybackType());
323        assertEquals(attrs, info.getAudioAttributes());
324        AudioManager manager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
325
326        int localVolumeControlType = VOLUME_CONTROL_ABSOLUTE;
327        if (Build.VERSION.SDK_INT >= 21 && manager.isVolumeFixed()) {
328            localVolumeControlType = VOLUME_CONTROL_FIXED;
329        }
330        assertEquals(localVolumeControlType, info.getControlType());
331        assertEquals(manager.getStreamMaxVolume(AudioManager.STREAM_MUSIC), info.getMaxVolume());
332        assertEquals(manager.getStreamVolume(AudioManager.STREAM_MUSIC), info.getCurrentVolume());
333
334        mSession.updatePlayer(player, null, volumeProvider);
335        assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
336
337        info = controller.getPlaybackInfo();
338        assertNotNull(info);
339        assertEquals(PlaybackInfo.PLAYBACK_TYPE_REMOTE, info.getPlaybackType());
340        assertEquals(attrs, info.getAudioAttributes());
341        assertEquals(volumeControlType, info.getControlType());
342        assertEquals(maxVolume, info.getMaxVolume());
343        assertEquals(currentVolume, info.getCurrentVolume());
344    }
345
346    @Test
347    public void testPlay() throws Exception {
348        prepareLooper();
349        mSession.play();
350        assertTrue(mPlayer.mPlayCalled);
351    }
352
353    @Test
354    public void testPause() throws Exception {
355        prepareLooper();
356        mSession.pause();
357        assertTrue(mPlayer.mPauseCalled);
358    }
359
360    @Test
361    public void testReset() throws Exception {
362        prepareLooper();
363        mSession.reset();
364        assertTrue(mPlayer.mResetCalled);
365    }
366
367    @Test
368    public void testPrepare() throws Exception {
369        prepareLooper();
370        mSession.prepare();
371        assertTrue(mPlayer.mPrepareCalled);
372    }
373
374    @Test
375    public void testSeekTo() throws Exception {
376        prepareLooper();
377        final long pos = 1004L;
378        mSession.seekTo(pos);
379        assertTrue(mPlayer.mSeekToCalled);
380        assertEquals(pos, mPlayer.mSeekPosition);
381    }
382
383    @Test
384    public void testSkipToPreviousItem() {
385        prepareLooper();
386        mSession.skipToPreviousItem();
387        assertTrue(mMockAgent.mSkipToPreviousItemCalled);
388    }
389
390    @Test
391    public void testSkipToNextItem() throws Exception {
392        prepareLooper();
393        mSession.skipToNextItem();
394        assertTrue(mMockAgent.mSkipToNextItemCalled);
395    }
396
397    @Test
398    public void testSkipToPlaylistItem() throws Exception {
399        prepareLooper();
400        final MediaItem2 testMediaItem = TestUtils.createMediaItemWithMetadata();
401        mSession.skipToPlaylistItem(testMediaItem);
402        assertTrue(mMockAgent.mSkipToPlaylistItemCalled);
403        assertSame(testMediaItem, mMockAgent.mItem);
404    }
405
406    @Test
407    public void testGetPlayerState() {
408        prepareLooper();
409        final int state = MediaPlayerBase.PLAYER_STATE_PLAYING;
410        mPlayer.mLastPlayerState = state;
411        assertEquals(state, mSession.getPlayerState());
412    }
413
414    @Test
415    public void testGetPosition() {
416        prepareLooper();
417        final long position = 150000;
418        mPlayer.mCurrentPosition = position;
419        assertEquals(position, mSession.getCurrentPosition());
420    }
421
422    @Test
423    public void testGetBufferedPosition() {
424        prepareLooper();
425        final long bufferedPosition = 900000;
426        mPlayer.mBufferedPosition = bufferedPosition;
427        assertEquals(bufferedPosition, mSession.getBufferedPosition());
428    }
429
430    @Test
431    public void testSetPlaylist() {
432        prepareLooper();
433        final List<MediaItem2> list = TestUtils.createPlaylist(2);
434        mSession.setPlaylist(list, null);
435        assertTrue(mMockAgent.mSetPlaylistCalled);
436        assertSame(list, mMockAgent.mPlaylist);
437        assertNull(mMockAgent.mMetadata);
438    }
439
440    @Test
441    public void testGetPlaylist() {
442        prepareLooper();
443        final List<MediaItem2> list = TestUtils.createPlaylist(2);
444        mMockAgent.mPlaylist = list;
445        assertEquals(list, mSession.getPlaylist());
446    }
447
448    @Test
449    public void testUpdatePlaylistMetadata() {
450        prepareLooper();
451        final MediaMetadata2 testMetadata = TestUtils.createMetadata();
452        mSession.updatePlaylistMetadata(testMetadata);
453        assertTrue(mMockAgent.mUpdatePlaylistMetadataCalled);
454        assertSame(testMetadata, mMockAgent.mMetadata);
455    }
456
457    @Test
458    public void testGetPlaylistMetadata() {
459        prepareLooper();
460        final MediaMetadata2 testMetadata = TestUtils.createMetadata();
461        mMockAgent.mMetadata = testMetadata;
462        assertEquals(testMetadata, mSession.getPlaylistMetadata());
463    }
464
465    @Test
466    public void testSessionCallback_onPlaylistChanged() throws InterruptedException {
467        prepareLooper();
468        final List<MediaItem2> list = TestUtils.createPlaylist(2);
469        final CountDownLatch latch = new CountDownLatch(1);
470        mMockAgent.setPlaylist(list, null);
471
472        final SessionCallback sessionCallback = new SessionCallback() {
473            @Override
474            public void onPlaylistChanged(MediaSession2 session, MediaPlaylistAgent playlistAgent,
475                    List<MediaItem2> playlist, MediaMetadata2 metadata) {
476                assertEquals(mMockAgent, playlistAgent);
477                assertEquals(list, playlist);
478                assertNull(metadata);
479                latch.countDown();
480            }
481        };
482        try (MediaSession2 session = new MediaSession2.Builder(mContext)
483                .setPlayer(mPlayer)
484                .setPlaylistAgent(mMockAgent)
485                .setId("testSessionCallback")
486                .setSessionCallback(sHandlerExecutor, sessionCallback)
487                .build()) {
488            mMockAgent.notifyPlaylistChanged();
489            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
490        }
491    }
492
493    @Test
494    public void testAddPlaylistItem() {
495        prepareLooper();
496        final int testIndex = 12;
497        final MediaItem2 testMediaItem = TestUtils.createMediaItemWithMetadata();
498        mSession.addPlaylistItem(testIndex, testMediaItem);
499        assertTrue(mMockAgent.mAddPlaylistItemCalled);
500        assertEquals(testIndex, mMockAgent.mIndex);
501        assertSame(testMediaItem, mMockAgent.mItem);
502    }
503
504    @Test
505    public void testRemovePlaylistItem() {
506        prepareLooper();
507        final MediaItem2 testMediaItem = TestUtils.createMediaItemWithMetadata();
508        mSession.removePlaylistItem(testMediaItem);
509        assertTrue(mMockAgent.mRemovePlaylistItemCalled);
510        assertSame(testMediaItem, mMockAgent.mItem);
511    }
512
513    @Test
514    public void testReplacePlaylistItem() throws InterruptedException {
515        prepareLooper();
516        final int testIndex = 12;
517        final MediaItem2 testMediaItem = TestUtils.createMediaItemWithMetadata();
518        mSession.replacePlaylistItem(testIndex, testMediaItem);
519        assertTrue(mMockAgent.mReplacePlaylistItemCalled);
520        assertEquals(testIndex, mMockAgent.mIndex);
521        assertSame(testMediaItem, mMockAgent.mItem);
522    }
523
524    /**
525     * This also tests {@link SessionCallback#onShuffleModeChanged}
526     */
527    @Test
528    public void testGetShuffleMode() throws InterruptedException {
529        prepareLooper();
530        final int testShuffleMode = MediaPlaylistAgent.SHUFFLE_MODE_GROUP;
531        mMockAgent.setShuffleMode(testShuffleMode);
532
533        final CountDownLatch latch = new CountDownLatch(1);
534        final SessionCallback sessionCallback = new SessionCallback() {
535            @Override
536            public void onShuffleModeChanged(MediaSession2 session,
537                    MediaPlaylistAgent playlistAgent, int shuffleMode) {
538                assertEquals(mMockAgent, playlistAgent);
539                assertEquals(testShuffleMode, shuffleMode);
540                latch.countDown();
541            }
542        };
543        try (MediaSession2 session = new MediaSession2.Builder(mContext)
544                .setPlayer(mPlayer)
545                .setPlaylistAgent(mMockAgent)
546                .setId("testGetShuffleMode")
547                .setSessionCallback(sHandlerExecutor, sessionCallback)
548                .build()) {
549            mMockAgent.notifyShuffleModeChanged();
550            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
551        }
552    }
553
554    @Test
555    public void testSetShuffleMode() {
556        prepareLooper();
557        final int testShuffleMode = MediaPlaylistAgent.SHUFFLE_MODE_GROUP;
558        mSession.setShuffleMode(testShuffleMode);
559        assertTrue(mMockAgent.mSetShuffleModeCalled);
560        assertEquals(testShuffleMode, mMockAgent.mShuffleMode);
561    }
562
563    /**
564     * This also tests {@link SessionCallback#onShuffleModeChanged}
565     */
566    @Test
567    public void testGetRepeatMode() throws InterruptedException {
568        prepareLooper();
569        final int testRepeatMode = MediaPlaylistAgent.REPEAT_MODE_GROUP;
570        mMockAgent.setRepeatMode(testRepeatMode);
571
572        final CountDownLatch latch = new CountDownLatch(1);
573        final SessionCallback sessionCallback = new SessionCallback() {
574            @Override
575            public void onRepeatModeChanged(MediaSession2 session, MediaPlaylistAgent playlistAgent,
576                    int repeatMode) {
577                assertEquals(mMockAgent, playlistAgent);
578                assertEquals(testRepeatMode, repeatMode);
579                latch.countDown();
580            }
581        };
582        try (MediaSession2 session = new MediaSession2.Builder(mContext)
583                .setPlayer(mPlayer)
584                .setPlaylistAgent(mMockAgent)
585                .setId("testGetRepeatMode")
586                .setSessionCallback(sHandlerExecutor, sessionCallback)
587                .build()) {
588            mMockAgent.notifyRepeatModeChanged();
589            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
590        }
591    }
592
593    @Test
594    public void testSetRepeatMode() {
595        prepareLooper();
596        final int testRepeatMode = MediaPlaylistAgent.REPEAT_MODE_GROUP;
597        mSession.setRepeatMode(testRepeatMode);
598        assertTrue(mMockAgent.mSetRepeatModeCalled);
599        assertEquals(testRepeatMode, mMockAgent.mRepeatMode);
600    }
601
602    // TODO(jaewan): Revisit
603    @Ignore
604    @Test
605    public void testBadPlayer() throws InterruptedException {
606        prepareLooper();
607        // TODO(jaewan): Add equivalent tests again
608        final CountDownLatch latch = new CountDownLatch(4); // expected call + 1
609        final BadPlayer player = new BadPlayer(0);
610
611        mSession.updatePlayer(player, null, null);
612        mSession.updatePlayer(mPlayer, null, null);
613        player.notifyPlaybackState(MediaPlayerBase.PLAYER_STATE_PAUSED);
614        assertFalse(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
615    }
616
617    // This bad player will keep push events to the listener that is previously
618    // registered by session.setPlayer().
619    private static class BadPlayer extends MockPlayer {
620        BadPlayer(int count) {
621            super(count);
622        }
623
624        @Override
625        public void unregisterPlayerEventCallback(
626                @NonNull MediaPlayerBase.PlayerEventCallback listener) {
627            // No-op.
628        }
629    }
630
631    @Test
632    public void testOnCommandCallback() throws InterruptedException {
633        prepareLooper();
634        final MockOnCommandCallback callback = new MockOnCommandCallback();
635        sHandler.postAndSync(new Runnable() {
636            @Override
637            public void run() {
638                mSession.close();
639                mPlayer = new MockPlayer(1);
640                mSession = new MediaSession2.Builder(mContext).setPlayer(mPlayer)
641                        .setSessionCallback(sHandlerExecutor, callback).build();
642            }
643        });
644        MediaController2 controller = createController(mSession.getToken());
645        controller.pause();
646        assertFalse(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
647        assertFalse(mPlayer.mPauseCalled);
648        assertEquals(1, callback.commands.size());
649        assertEquals(SessionCommand2.COMMAND_CODE_PLAYBACK_PAUSE,
650                (long) callback.commands.get(0).getCommandCode());
651
652        controller.play();
653        assertTrue(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
654        assertTrue(mPlayer.mPlayCalled);
655        assertFalse(mPlayer.mPauseCalled);
656        assertEquals(2, callback.commands.size());
657        assertEquals(SessionCommand2.COMMAND_CODE_PLAYBACK_PLAY,
658                (long) callback.commands.get(1).getCommandCode());
659    }
660
661    @Test
662    public void testOnConnectCallback() throws InterruptedException {
663        prepareLooper();
664        final MockOnConnectCallback sessionCallback = new MockOnConnectCallback();
665        sHandler.postAndSync(new Runnable() {
666            @Override
667            public void run() {
668                mSession.close();
669                mSession = new MediaSession2.Builder(mContext).setPlayer(mPlayer)
670                        .setSessionCallback(sHandlerExecutor, sessionCallback).build();
671            }
672        });
673        MediaController2 controller = createController(mSession.getToken(), false, null);
674        assertNotNull(controller);
675        waitForConnect(controller, false);
676        waitForDisconnect(controller, true);
677    }
678
679    @Test
680    public void testOnDisconnectCallback() throws InterruptedException {
681        prepareLooper();
682        final CountDownLatch latch = new CountDownLatch(1);
683        try (MediaSession2 session = new MediaSession2.Builder(mContext)
684                .setPlayer(mPlayer)
685                .setId("testOnDisconnectCallback")
686                .setSessionCallback(sHandlerExecutor, new SessionCallback() {
687                    @Override
688                    public void onDisconnected(MediaSession2 session,
689                            ControllerInfo controller) {
690                        assertEquals(Process.myUid(), controller.getUid());
691                        latch.countDown();
692                    }
693                }).build()) {
694            MediaController2 controller = createController(session.getToken());
695            controller.close();
696            assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
697        }
698    }
699
700    @Test
701    public void testSetCustomLayout() throws InterruptedException {
702        prepareLooper();
703        final List<CommandButton> buttons = new ArrayList<>();
704        buttons.add(new CommandButton.Builder()
705                .setCommand(new SessionCommand2(SessionCommand2.COMMAND_CODE_PLAYBACK_PLAY))
706                .setDisplayName("button").build());
707        final CountDownLatch latch = new CountDownLatch(1);
708        final SessionCallback sessionCallback = new SessionCallback() {
709            @Override
710            public SessionCommandGroup2 onConnect(MediaSession2 session,
711                    ControllerInfo controller) {
712                if (mContext.getPackageName().equals(controller.getPackageName())) {
713                    mSession.setCustomLayout(controller, buttons);
714                }
715                return super.onConnect(session, controller);
716            }
717        };
718
719        try (MediaSession2 session = new MediaSession2.Builder(mContext)
720                .setPlayer(mPlayer)
721                .setId("testSetCustomLayout")
722                .setSessionCallback(sHandlerExecutor, sessionCallback)
723                .build()) {
724            if (mSession != null) {
725                mSession.close();
726                mSession = session;
727            }
728            final ControllerCallback callback = new ControllerCallback() {
729                @Override
730                public void onCustomLayoutChanged(MediaController2 controller2,
731                        List<CommandButton> layout) {
732                    assertEquals(layout.size(), buttons.size());
733                    for (int i = 0; i < layout.size(); i++) {
734                        assertEquals(layout.get(i).getCommand(), buttons.get(i).getCommand());
735                        assertEquals(layout.get(i).getDisplayName(),
736                                buttons.get(i).getDisplayName());
737                    }
738                    latch.countDown();
739                }
740            };
741            final MediaController2 controller =
742                    createController(session.getToken(), true, callback);
743            assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
744        }
745    }
746
747    @Test
748    public void testSetAllowedCommands() throws InterruptedException {
749        prepareLooper();
750        final SessionCommandGroup2 commands = new SessionCommandGroup2();
751        commands.addCommand(new SessionCommand2(SessionCommand2.COMMAND_CODE_PLAYBACK_PLAY));
752        commands.addCommand(new SessionCommand2(SessionCommand2.COMMAND_CODE_PLAYBACK_PAUSE));
753        commands.addCommand(new SessionCommand2(SessionCommand2.COMMAND_CODE_PLAYBACK_RESET));
754
755        final CountDownLatch latch = new CountDownLatch(1);
756        final ControllerCallback callback = new ControllerCallback() {
757            @Override
758            public void onAllowedCommandsChanged(MediaController2 controller,
759                    SessionCommandGroup2 commandsOut) {
760                assertNotNull(commandsOut);
761                Set<SessionCommand2> expected = commands.getCommands();
762                Set<SessionCommand2> actual = commandsOut.getCommands();
763
764                assertNotNull(actual);
765                assertEquals(expected.size(), actual.size());
766                for (SessionCommand2 command : expected) {
767                    assertTrue(actual.contains(command));
768                }
769                latch.countDown();
770            }
771        };
772
773        final MediaController2 controller = createController(mSession.getToken(), true, callback);
774        ControllerInfo controllerInfo = getTestControllerInfo();
775        assertNotNull(controllerInfo);
776
777        mSession.setAllowedCommands(controllerInfo, commands);
778        assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
779    }
780
781    @Test
782    public void testSendCustomCommand() throws InterruptedException {
783        prepareLooper();
784        final SessionCommand2 testCommand = new SessionCommand2(
785                SessionCommand2.COMMAND_CODE_PLAYBACK_PREPARE);
786        final Bundle testArgs = new Bundle();
787        testArgs.putString("args", "testSendCustomAction");
788
789        final CountDownLatch latch = new CountDownLatch(2);
790        final ControllerCallback callback = new ControllerCallback() {
791            @Override
792            public void onCustomCommand(MediaController2 controller, SessionCommand2 command,
793                    Bundle args, ResultReceiver receiver) {
794                assertEquals(testCommand, command);
795                assertTrue(TestUtils.equals(testArgs, args));
796                assertNull(receiver);
797                latch.countDown();
798            }
799        };
800        final MediaController2 controller =
801                createController(mSession.getToken(), true, callback);
802        // TODO(jaewan): Test with multiple controllers
803        mSession.sendCustomCommand(testCommand, testArgs);
804
805        ControllerInfo controllerInfo = getTestControllerInfo();
806        assertNotNull(controllerInfo);
807        // TODO(jaewan): Test receivers as well.
808        mSession.sendCustomCommand(controllerInfo, testCommand, testArgs, null);
809        assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
810    }
811
812    @Test
813    public void testNotifyError() throws InterruptedException {
814        prepareLooper();
815        final int errorCode = MediaSession2.ERROR_CODE_NOT_AVAILABLE_IN_REGION;
816        final Bundle extras = new Bundle();
817        extras.putString("args", "testNotifyError");
818
819        final CountDownLatch latch = new CountDownLatch(1);
820        final ControllerCallback callback = new ControllerCallback() {
821            @Override
822            public void onError(MediaController2 controller, int errorCodeOut, Bundle extrasOut) {
823                assertEquals(errorCode, errorCodeOut);
824                assertTrue(TestUtils.equals(extras, extrasOut));
825                latch.countDown();
826            }
827        };
828        final MediaController2 controller = createController(mSession.getToken(), true, callback);
829        // TODO(jaewan): Test with multiple controllers
830        mSession.notifyError(errorCode, extras);
831        assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
832    }
833
834    @Test
835    public void testNotifyRoutesInfoChanged() throws InterruptedException {
836        prepareLooper();
837        final CountDownLatch latch = new CountDownLatch(1);
838        final ControllerCallback callback = new ControllerCallback() {
839            @Override
840            public void onRoutesInfoChanged(@NonNull MediaController2 controller,
841                    @Nullable List<Bundle> routes) {
842                assertNull(routes);
843                latch.countDown();
844            }
845        };
846        final MediaController2 controller = createController(mSession.getToken(), true, callback);
847        ControllerInfo controllerInfo = getTestControllerInfo();
848        mSession.notifyRoutesInfoChanged(controllerInfo, null);
849        assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
850    }
851
852    private ControllerInfo getTestControllerInfo() {
853        List<ControllerInfo> controllers = mSession.getConnectedControllers();
854        assertNotNull(controllers);
855        for (int i = 0; i < controllers.size(); i++) {
856            if (Process.myUid() == controllers.get(i).getUid()) {
857                return controllers.get(i);
858            }
859        }
860        fail("Failed to get test controller info");
861        return null;
862    }
863
864    public class MockOnConnectCallback extends SessionCallback {
865        @Override
866        public SessionCommandGroup2 onConnect(MediaSession2 session,
867                ControllerInfo controllerInfo) {
868            if (Process.myUid() != controllerInfo.getUid()) {
869                return null;
870            }
871            assertEquals(mContext.getPackageName(), controllerInfo.getPackageName());
872            assertEquals(Process.myUid(), controllerInfo.getUid());
873            assertFalse(controllerInfo.isTrusted());
874            // Reject all
875            return null;
876        }
877    }
878
879    public class MockOnCommandCallback extends SessionCallback {
880        public final ArrayList<SessionCommand2> commands = new ArrayList<>();
881
882        @Override
883        public boolean onCommandRequest(MediaSession2 session, ControllerInfo controllerInfo,
884                SessionCommand2 command) {
885            assertEquals(mContext.getPackageName(), controllerInfo.getPackageName());
886            assertEquals(Process.myUid(), controllerInfo.getUid());
887            assertFalse(controllerInfo.isTrusted());
888            commands.add(command);
889            if (command.getCommandCode() == SessionCommand2.COMMAND_CODE_PLAYBACK_PAUSE) {
890                return false;
891            }
892            return true;
893        }
894    }
895
896    private static void assertMediaItemListEquals(List<MediaItem2> a, List<MediaItem2> b) {
897        if (a == null || b == null) {
898            assertEquals(a, b);
899        }
900        assertEquals(a.size(), b.size());
901
902        for (int i = 0; i < a.size(); i++) {
903            MediaItem2 aItem = a.get(i);
904            MediaItem2 bItem = b.get(i);
905
906            if (aItem == null || bItem == null) {
907                assertEquals(aItem, bItem);
908                continue;
909            }
910
911            assertEquals(aItem.getMediaId(), bItem.getMediaId());
912            assertEquals(aItem.getFlags(), bItem.getFlags());
913            TestUtils.equals(aItem.getMetadata().toBundle(), bItem.getMetadata().toBundle());
914
915            // Note: Here it does not check whether DataSourceDesc are equal,
916            // since there DataSourceDec is not comparable.
917        }
918    }
919}
920