MediaSession2Test.java revision 3a151f1283d08b40c12e4a17903ba421f23c4342
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            final CountDownLatch latchForControllerCallback = new CountDownLatch(1);
186            final MediaController2 controller =
187                    createController(mSession.getToken(), true, new ControllerCallback() {
188                        @Override
189                        public void onCurrentMediaItemChanged(MediaController2 controller,
190                                MediaItem2 item) {
191                            assertEquals(currentItem, item);
192                            latchForControllerCallback.countDown();
193                        }
194                    });
195
196            mPlayer.notifyCurrentDataSourceChanged(currentItem.getDataSourceDesc());
197            assertTrue(latchForSessionCallback.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
198            assertTrue(latchForControllerCallback.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
199            assertEquals(currentItem, controller.getCurrentMediaItem());
200        }
201    }
202
203    @Test
204    public void testMediaPrepared() throws Exception {
205        prepareLooper();
206        final int listSize = 5;
207        final List<MediaItem2> list = TestUtils.createPlaylist(listSize);
208        mMockAgent.setPlaylist(list, null);
209
210        final MediaItem2 currentItem = list.get(3);
211
212        final CountDownLatch latchForSessionCallback = new CountDownLatch(1);
213        try (MediaSession2 session = new MediaSession2.Builder(mContext)
214                .setPlayer(mPlayer)
215                .setPlaylistAgent(mMockAgent)
216                .setId("testMediaPrepared")
217                .setSessionCallback(sHandlerExecutor, new SessionCallback() {
218                    @Override
219                    public void onMediaPrepared(MediaSession2 session, MediaPlayerBase player,
220                            MediaItem2 itemOut) {
221                        assertSame(currentItem, itemOut);
222                        latchForSessionCallback.countDown();
223                    }
224                }).build()) {
225
226            mPlayer.notifyMediaPrepared(currentItem.getDataSourceDesc());
227            assertTrue(latchForSessionCallback.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
228            // TODO(jaewan): Test that controllers are also notified. (b/74505936)
229        }
230    }
231
232    @Test
233    public void testBufferingStateChanged() throws Exception {
234        prepareLooper();
235        final int listSize = 5;
236        final List<MediaItem2> list = TestUtils.createPlaylist(listSize);
237        mMockAgent.setPlaylist(list, null);
238
239        final MediaItem2 currentItem = list.get(3);
240        final int buffState = MediaPlayerBase.BUFFERING_STATE_BUFFERING_COMPLETE;
241
242        final CountDownLatch latchForSessionCallback = new CountDownLatch(1);
243        try (MediaSession2 session = new MediaSession2.Builder(mContext)
244                .setPlayer(mPlayer)
245                .setPlaylistAgent(mMockAgent)
246                .setId("testBufferingStateChanged")
247                .setSessionCallback(sHandlerExecutor, new SessionCallback() {
248                    @Override
249                    public void onBufferingStateChanged(MediaSession2 session,
250                            MediaPlayerBase player, MediaItem2 itemOut, int stateOut) {
251                        assertSame(currentItem, itemOut);
252                        assertEquals(buffState, stateOut);
253                        latchForSessionCallback.countDown();
254                    }
255                }).build()) {
256
257            mPlayer.notifyBufferingStateChanged(currentItem.getDataSourceDesc(), buffState);
258            assertTrue(latchForSessionCallback.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
259            // TODO(jaewan): Test that controllers are also notified. (b/74505936)
260        }
261    }
262
263    /**
264     * This also tests {@link ControllerCallback#onPlaybackSpeedChanged(MediaController2, float)}
265     * and {@link MediaController2#getPlaybackSpeed()}.
266     */
267    @Test
268    public void testPlaybackSpeedChanged() throws Exception {
269        prepareLooper();
270        final float speed = 1.5f;
271        mPlayer.setPlaybackSpeed(speed);
272
273        final CountDownLatch latchForSessionCallback = new CountDownLatch(1);
274        try (MediaSession2 session = new MediaSession2.Builder(mContext)
275                .setPlayer(mPlayer)
276                .setId("testPlaybackSpeedChanged")
277                .setSessionCallback(sHandlerExecutor, new SessionCallback() {
278                    @Override
279                    public void onPlaybackSpeedChanged(MediaSession2 session,
280                            MediaPlayerBase player, float speedOut) {
281                        assertEquals(speed, speedOut, 0.0f);
282                        latchForSessionCallback.countDown();
283                    }
284                }).build()) {
285
286            final CountDownLatch latchForControllerCallback = new CountDownLatch(1);
287            final MediaController2 controller =
288                    createController(mSession.getToken(), true, new ControllerCallback() {
289                        @Override
290                        public void onPlaybackSpeedChanged(MediaController2 controller,
291                                float speedOut) {
292                            assertEquals(speed, speedOut, 0.0f);
293                            latchForControllerCallback.countDown();
294                        }
295                    });
296
297            mPlayer.notifyPlaybackSpeedChanged(speed);
298            assertTrue(latchForSessionCallback.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
299            assertTrue(latchForControllerCallback.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
300            assertEquals(speed, controller.getPlaybackSpeed(), 0.0f);
301        }
302    }
303
304    @Test
305    public void testUpdatePlayer() throws Exception {
306        prepareLooper();
307        final int targetState = MediaPlayerBase.PLAYER_STATE_PLAYING;
308        final CountDownLatch latch = new CountDownLatch(1);
309        sHandler.postAndSync(new Runnable() {
310            @Override
311            public void run() {
312                mSession.close();
313                mSession = new MediaSession2.Builder(mContext).setPlayer(mPlayer)
314                        .setSessionCallback(sHandlerExecutor, new SessionCallback() {
315                            @Override
316                            public void onPlayerStateChanged(MediaSession2 session,
317                                    MediaPlayerBase player, int state) {
318                                assertEquals(targetState, state);
319                                latch.countDown();
320                            }
321                        }).build();
322            }
323        });
324
325        MockPlayer player = new MockPlayer(0);
326
327        // Test if setPlayer doesn't crash with various situations.
328        mSession.updatePlayer(mPlayer, null, null);
329        assertEquals(mPlayer, mSession.getPlayer());
330        MediaPlaylistAgent agent = mSession.getPlaylistAgent();
331        assertNotNull(agent);
332
333        mSession.updatePlayer(player, null, null);
334        assertEquals(player, mSession.getPlayer());
335        assertNotNull(mSession.getPlaylistAgent());
336        assertNotEquals(agent, mSession.getPlaylistAgent());
337
338        player.notifyPlaybackState(MediaPlayerBase.PLAYER_STATE_PLAYING);
339        assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
340    }
341
342    @Test
343    public void testSetPlayer_playbackInfo() throws Exception {
344        prepareLooper();
345        MockPlayer player = new MockPlayer(0);
346        final AudioAttributesCompat attrs = new AudioAttributesCompat.Builder()
347                .setContentType(CONTENT_TYPE_MUSIC)
348                .build();
349        player.setAudioAttributes(attrs);
350
351        final int maxVolume = 100;
352        final int currentVolume = 23;
353        final int volumeControlType = VOLUME_CONTROL_ABSOLUTE;
354        VolumeProviderCompat volumeProvider = new VolumeProviderCompat(
355                volumeControlType, maxVolume, currentVolume) { };
356
357        final CountDownLatch latch = new CountDownLatch(1);
358        final ControllerCallback callback = new ControllerCallback() {
359            @Override
360            public void onPlaybackInfoChanged(MediaController2 controller, PlaybackInfo info) {
361                Assert.assertEquals(PlaybackInfo.PLAYBACK_TYPE_REMOTE, info.getPlaybackType());
362                assertEquals(attrs, info.getAudioAttributes());
363                assertEquals(volumeControlType, info.getPlaybackType());
364                assertEquals(maxVolume, info.getMaxVolume());
365                assertEquals(currentVolume, info.getCurrentVolume());
366                latch.countDown();
367            }
368        };
369
370        mSession.updatePlayer(player, null, null);
371
372        final MediaController2 controller = createController(mSession.getToken(), true, callback);
373        PlaybackInfo info = controller.getPlaybackInfo();
374        assertNotNull(info);
375        assertEquals(PlaybackInfo.PLAYBACK_TYPE_LOCAL, info.getPlaybackType());
376        assertEquals(attrs, info.getAudioAttributes());
377        AudioManager manager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
378
379        int localVolumeControlType = VOLUME_CONTROL_ABSOLUTE;
380        if (Build.VERSION.SDK_INT >= 21 && manager.isVolumeFixed()) {
381            localVolumeControlType = VOLUME_CONTROL_FIXED;
382        }
383        assertEquals(localVolumeControlType, info.getControlType());
384        assertEquals(manager.getStreamMaxVolume(AudioManager.STREAM_MUSIC), info.getMaxVolume());
385        assertEquals(manager.getStreamVolume(AudioManager.STREAM_MUSIC), info.getCurrentVolume());
386
387        mSession.updatePlayer(player, null, volumeProvider);
388        assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
389
390        info = controller.getPlaybackInfo();
391        assertNotNull(info);
392        assertEquals(PlaybackInfo.PLAYBACK_TYPE_REMOTE, info.getPlaybackType());
393        assertEquals(attrs, info.getAudioAttributes());
394        assertEquals(volumeControlType, info.getControlType());
395        assertEquals(maxVolume, info.getMaxVolume());
396        assertEquals(currentVolume, info.getCurrentVolume());
397    }
398
399    @Test
400    public void testPlay() throws Exception {
401        prepareLooper();
402        mSession.play();
403        assertTrue(mPlayer.mPlayCalled);
404    }
405
406    @Test
407    public void testPause() throws Exception {
408        prepareLooper();
409        mSession.pause();
410        assertTrue(mPlayer.mPauseCalled);
411    }
412
413    @Test
414    public void testReset() throws Exception {
415        prepareLooper();
416        mSession.reset();
417        assertTrue(mPlayer.mResetCalled);
418    }
419
420    @Test
421    public void testPrepare() throws Exception {
422        prepareLooper();
423        mSession.prepare();
424        assertTrue(mPlayer.mPrepareCalled);
425    }
426
427    @Test
428    public void testSeekTo() throws Exception {
429        prepareLooper();
430        final long pos = 1004L;
431        mSession.seekTo(pos);
432        assertTrue(mPlayer.mSeekToCalled);
433        assertEquals(pos, mPlayer.mSeekPosition);
434    }
435
436    @Test
437    public void testSetPlaybackSpeed() throws Exception {
438        prepareLooper();
439        final float speed = 1.5f;
440        mSession.setPlaybackSpeed(speed);
441        assertTrue(mPlayer.mSetPlaybackSpeedCalled);
442        assertEquals(speed, mPlayer.mPlaybackSpeed, 0.0f);
443    }
444
445    @Test
446    public void testGetPlaybackSpeed() throws Exception {
447        prepareLooper();
448        final float speed = 1.5f;
449        mPlayer.setPlaybackSpeed(speed);
450        assertEquals(speed, mSession.getPlaybackSpeed(), 0.0f);
451    }
452
453    @Test
454    public void testGetCurrentMediaItem() {
455        prepareLooper();
456        MediaItem2 item = TestUtils.createMediaItemWithMetadata();
457        mMockAgent.mCurrentMediaItem = item;
458        assertEquals(item, mSession.getCurrentMediaItem());
459    }
460
461    @Test
462    public void testSkipToPreviousItem() {
463        prepareLooper();
464        mSession.skipToPreviousItem();
465        assertTrue(mMockAgent.mSkipToPreviousItemCalled);
466    }
467
468    @Test
469    public void testSkipToNextItem() throws Exception {
470        prepareLooper();
471        mSession.skipToNextItem();
472        assertTrue(mMockAgent.mSkipToNextItemCalled);
473    }
474
475    @Test
476    public void testSkipToPlaylistItem() throws Exception {
477        prepareLooper();
478        final MediaItem2 testMediaItem = TestUtils.createMediaItemWithMetadata();
479        mSession.skipToPlaylistItem(testMediaItem);
480        assertTrue(mMockAgent.mSkipToPlaylistItemCalled);
481        assertSame(testMediaItem, mMockAgent.mItem);
482    }
483
484    @Test
485    public void testGetPlayerState() {
486        prepareLooper();
487        final int state = MediaPlayerBase.PLAYER_STATE_PLAYING;
488        mPlayer.mLastPlayerState = state;
489        assertEquals(state, mSession.getPlayerState());
490    }
491
492    @Test
493    public void testGetPosition() {
494        prepareLooper();
495        final long position = 150000;
496        mPlayer.mCurrentPosition = position;
497        assertEquals(position, mSession.getCurrentPosition());
498    }
499
500    @Test
501    public void testGetBufferedPosition() {
502        prepareLooper();
503        final long bufferedPosition = 900000;
504        mPlayer.mBufferedPosition = bufferedPosition;
505        assertEquals(bufferedPosition, mSession.getBufferedPosition());
506    }
507
508    @Test
509    public void testSetPlaylist() {
510        prepareLooper();
511        final List<MediaItem2> list = TestUtils.createPlaylist(2);
512        mSession.setPlaylist(list, null);
513        assertTrue(mMockAgent.mSetPlaylistCalled);
514        assertSame(list, mMockAgent.mPlaylist);
515        assertNull(mMockAgent.mMetadata);
516    }
517
518    @Test
519    public void testGetPlaylist() {
520        prepareLooper();
521        final List<MediaItem2> list = TestUtils.createPlaylist(2);
522        mMockAgent.mPlaylist = list;
523        assertEquals(list, mSession.getPlaylist());
524    }
525
526    @Test
527    public void testUpdatePlaylistMetadata() {
528        prepareLooper();
529        final MediaMetadata2 testMetadata = TestUtils.createMetadata();
530        mSession.updatePlaylistMetadata(testMetadata);
531        assertTrue(mMockAgent.mUpdatePlaylistMetadataCalled);
532        assertSame(testMetadata, mMockAgent.mMetadata);
533    }
534
535    @Test
536    public void testGetPlaylistMetadata() {
537        prepareLooper();
538        final MediaMetadata2 testMetadata = TestUtils.createMetadata();
539        mMockAgent.mMetadata = testMetadata;
540        assertEquals(testMetadata, mSession.getPlaylistMetadata());
541    }
542
543    @Test
544    public void testSessionCallback_onPlaylistChanged() throws InterruptedException {
545        prepareLooper();
546        final List<MediaItem2> list = TestUtils.createPlaylist(2);
547        final CountDownLatch latch = new CountDownLatch(1);
548        mMockAgent.setPlaylist(list, null);
549
550        final SessionCallback sessionCallback = new SessionCallback() {
551            @Override
552            public void onPlaylistChanged(MediaSession2 session, MediaPlaylistAgent playlistAgent,
553                    List<MediaItem2> playlist, MediaMetadata2 metadata) {
554                assertEquals(mMockAgent, playlistAgent);
555                assertEquals(list, playlist);
556                assertNull(metadata);
557                latch.countDown();
558            }
559        };
560        try (MediaSession2 session = new MediaSession2.Builder(mContext)
561                .setPlayer(mPlayer)
562                .setPlaylistAgent(mMockAgent)
563                .setId("testSessionCallback")
564                .setSessionCallback(sHandlerExecutor, sessionCallback)
565                .build()) {
566            mMockAgent.notifyPlaylistChanged();
567            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
568        }
569    }
570
571    @Test
572    public void testAddPlaylistItem() {
573        prepareLooper();
574        final int testIndex = 12;
575        final MediaItem2 testMediaItem = TestUtils.createMediaItemWithMetadata();
576        mSession.addPlaylistItem(testIndex, testMediaItem);
577        assertTrue(mMockAgent.mAddPlaylistItemCalled);
578        assertEquals(testIndex, mMockAgent.mIndex);
579        assertSame(testMediaItem, mMockAgent.mItem);
580    }
581
582    @Test
583    public void testRemovePlaylistItem() {
584        prepareLooper();
585        final MediaItem2 testMediaItem = TestUtils.createMediaItemWithMetadata();
586        mSession.removePlaylistItem(testMediaItem);
587        assertTrue(mMockAgent.mRemovePlaylistItemCalled);
588        assertSame(testMediaItem, mMockAgent.mItem);
589    }
590
591    @Test
592    public void testReplacePlaylistItem() throws InterruptedException {
593        prepareLooper();
594        final int testIndex = 12;
595        final MediaItem2 testMediaItem = TestUtils.createMediaItemWithMetadata();
596        mSession.replacePlaylistItem(testIndex, testMediaItem);
597        assertTrue(mMockAgent.mReplacePlaylistItemCalled);
598        assertEquals(testIndex, mMockAgent.mIndex);
599        assertSame(testMediaItem, mMockAgent.mItem);
600    }
601
602    /**
603     * This also tests {@link SessionCallback#onShuffleModeChanged}
604     */
605    @Test
606    public void testGetShuffleMode() throws InterruptedException {
607        prepareLooper();
608        final int testShuffleMode = MediaPlaylistAgent.SHUFFLE_MODE_GROUP;
609        mMockAgent.setShuffleMode(testShuffleMode);
610
611        final CountDownLatch latch = new CountDownLatch(1);
612        final SessionCallback sessionCallback = new SessionCallback() {
613            @Override
614            public void onShuffleModeChanged(MediaSession2 session,
615                    MediaPlaylistAgent playlistAgent, int shuffleMode) {
616                assertEquals(mMockAgent, playlistAgent);
617                assertEquals(testShuffleMode, shuffleMode);
618                latch.countDown();
619            }
620        };
621        try (MediaSession2 session = new MediaSession2.Builder(mContext)
622                .setPlayer(mPlayer)
623                .setPlaylistAgent(mMockAgent)
624                .setId("testGetShuffleMode")
625                .setSessionCallback(sHandlerExecutor, sessionCallback)
626                .build()) {
627            mMockAgent.notifyShuffleModeChanged();
628            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
629        }
630    }
631
632    @Test
633    public void testSetShuffleMode() {
634        prepareLooper();
635        final int testShuffleMode = MediaPlaylistAgent.SHUFFLE_MODE_GROUP;
636        mSession.setShuffleMode(testShuffleMode);
637        assertTrue(mMockAgent.mSetShuffleModeCalled);
638        assertEquals(testShuffleMode, mMockAgent.mShuffleMode);
639    }
640
641    /**
642     * This also tests {@link SessionCallback#onShuffleModeChanged}
643     */
644    @Test
645    public void testGetRepeatMode() throws InterruptedException {
646        prepareLooper();
647        final int testRepeatMode = MediaPlaylistAgent.REPEAT_MODE_GROUP;
648        mMockAgent.setRepeatMode(testRepeatMode);
649
650        final CountDownLatch latch = new CountDownLatch(1);
651        final SessionCallback sessionCallback = new SessionCallback() {
652            @Override
653            public void onRepeatModeChanged(MediaSession2 session, MediaPlaylistAgent playlistAgent,
654                    int repeatMode) {
655                assertEquals(mMockAgent, playlistAgent);
656                assertEquals(testRepeatMode, repeatMode);
657                latch.countDown();
658            }
659        };
660        try (MediaSession2 session = new MediaSession2.Builder(mContext)
661                .setPlayer(mPlayer)
662                .setPlaylistAgent(mMockAgent)
663                .setId("testGetRepeatMode")
664                .setSessionCallback(sHandlerExecutor, sessionCallback)
665                .build()) {
666            mMockAgent.notifyRepeatModeChanged();
667            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
668        }
669    }
670
671    @Test
672    public void testSetRepeatMode() {
673        prepareLooper();
674        final int testRepeatMode = MediaPlaylistAgent.REPEAT_MODE_GROUP;
675        mSession.setRepeatMode(testRepeatMode);
676        assertTrue(mMockAgent.mSetRepeatModeCalled);
677        assertEquals(testRepeatMode, mMockAgent.mRepeatMode);
678    }
679
680    // TODO(jaewan): Revisit
681    @Ignore
682    @Test
683    public void testBadPlayer() throws InterruptedException {
684        prepareLooper();
685        // TODO(jaewan): Add equivalent tests again
686        final CountDownLatch latch = new CountDownLatch(4); // expected call + 1
687        final BadPlayer player = new BadPlayer(0);
688
689        mSession.updatePlayer(player, null, null);
690        mSession.updatePlayer(mPlayer, null, null);
691        player.notifyPlaybackState(MediaPlayerBase.PLAYER_STATE_PAUSED);
692        assertFalse(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
693    }
694
695    // This bad player will keep push events to the listener that is previously
696    // registered by session.setPlayer().
697    private static class BadPlayer extends MockPlayer {
698        BadPlayer(int count) {
699            super(count);
700        }
701
702        @Override
703        public void unregisterPlayerEventCallback(
704                @NonNull MediaPlayerBase.PlayerEventCallback listener) {
705            // No-op.
706        }
707    }
708
709    @Test
710    public void testOnCommandCallback() throws InterruptedException {
711        prepareLooper();
712        final MockOnCommandCallback callback = new MockOnCommandCallback();
713        sHandler.postAndSync(new Runnable() {
714            @Override
715            public void run() {
716                mSession.close();
717                mPlayer = new MockPlayer(1);
718                mSession = new MediaSession2.Builder(mContext).setPlayer(mPlayer)
719                        .setSessionCallback(sHandlerExecutor, callback).build();
720            }
721        });
722        MediaController2 controller = createController(mSession.getToken());
723        controller.pause();
724        assertFalse(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
725        assertFalse(mPlayer.mPauseCalled);
726        assertEquals(1, callback.commands.size());
727        assertEquals(SessionCommand2.COMMAND_CODE_PLAYBACK_PAUSE,
728                (long) callback.commands.get(0).getCommandCode());
729
730        controller.play();
731        assertTrue(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
732        assertTrue(mPlayer.mPlayCalled);
733        assertFalse(mPlayer.mPauseCalled);
734        assertEquals(2, callback.commands.size());
735        assertEquals(SessionCommand2.COMMAND_CODE_PLAYBACK_PLAY,
736                (long) callback.commands.get(1).getCommandCode());
737    }
738
739    @Test
740    public void testOnConnectCallback() throws InterruptedException {
741        prepareLooper();
742        final MockOnConnectCallback sessionCallback = new MockOnConnectCallback();
743        sHandler.postAndSync(new Runnable() {
744            @Override
745            public void run() {
746                mSession.close();
747                mSession = new MediaSession2.Builder(mContext).setPlayer(mPlayer)
748                        .setSessionCallback(sHandlerExecutor, sessionCallback).build();
749            }
750        });
751        MediaController2 controller = createController(mSession.getToken(), false, null);
752        assertNotNull(controller);
753        waitForConnect(controller, false);
754        waitForDisconnect(controller, true);
755    }
756
757    @Test
758    public void testOnDisconnectCallback() throws InterruptedException {
759        prepareLooper();
760        final CountDownLatch latch = new CountDownLatch(1);
761        try (MediaSession2 session = new MediaSession2.Builder(mContext)
762                .setPlayer(mPlayer)
763                .setId("testOnDisconnectCallback")
764                .setSessionCallback(sHandlerExecutor, new SessionCallback() {
765                    @Override
766                    public void onDisconnected(MediaSession2 session,
767                            ControllerInfo controller) {
768                        assertEquals(Process.myUid(), controller.getUid());
769                        latch.countDown();
770                    }
771                }).build()) {
772            MediaController2 controller = createController(session.getToken());
773            controller.close();
774            assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
775        }
776    }
777
778    @Test
779    public void testSetCustomLayout() throws InterruptedException {
780        prepareLooper();
781        final List<CommandButton> buttons = new ArrayList<>();
782        buttons.add(new CommandButton.Builder()
783                .setCommand(new SessionCommand2(SessionCommand2.COMMAND_CODE_PLAYBACK_PLAY))
784                .setDisplayName("button").build());
785        final CountDownLatch latch = new CountDownLatch(1);
786        final SessionCallback sessionCallback = new SessionCallback() {
787            @Override
788            public SessionCommandGroup2 onConnect(MediaSession2 session,
789                    ControllerInfo controller) {
790                if (mContext.getPackageName().equals(controller.getPackageName())) {
791                    mSession.setCustomLayout(controller, buttons);
792                }
793                return super.onConnect(session, controller);
794            }
795        };
796
797        try (MediaSession2 session = new MediaSession2.Builder(mContext)
798                .setPlayer(mPlayer)
799                .setId("testSetCustomLayout")
800                .setSessionCallback(sHandlerExecutor, sessionCallback)
801                .build()) {
802            if (mSession != null) {
803                mSession.close();
804                mSession = session;
805            }
806            final ControllerCallback callback = new ControllerCallback() {
807                @Override
808                public void onCustomLayoutChanged(MediaController2 controller2,
809                        List<CommandButton> layout) {
810                    assertEquals(layout.size(), buttons.size());
811                    for (int i = 0; i < layout.size(); i++) {
812                        assertEquals(layout.get(i).getCommand(), buttons.get(i).getCommand());
813                        assertEquals(layout.get(i).getDisplayName(),
814                                buttons.get(i).getDisplayName());
815                    }
816                    latch.countDown();
817                }
818            };
819            final MediaController2 controller =
820                    createController(session.getToken(), true, callback);
821            assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
822        }
823    }
824
825    @Test
826    public void testSetAllowedCommands() throws InterruptedException {
827        prepareLooper();
828        final SessionCommandGroup2 commands = new SessionCommandGroup2();
829        commands.addCommand(new SessionCommand2(SessionCommand2.COMMAND_CODE_PLAYBACK_PLAY));
830        commands.addCommand(new SessionCommand2(SessionCommand2.COMMAND_CODE_PLAYBACK_PAUSE));
831        commands.addCommand(new SessionCommand2(SessionCommand2.COMMAND_CODE_PLAYBACK_RESET));
832
833        final CountDownLatch latch = new CountDownLatch(1);
834        final ControllerCallback callback = new ControllerCallback() {
835            @Override
836            public void onAllowedCommandsChanged(MediaController2 controller,
837                    SessionCommandGroup2 commandsOut) {
838                assertNotNull(commandsOut);
839                Set<SessionCommand2> expected = commands.getCommands();
840                Set<SessionCommand2> actual = commandsOut.getCommands();
841
842                assertNotNull(actual);
843                assertEquals(expected.size(), actual.size());
844                for (SessionCommand2 command : expected) {
845                    assertTrue(actual.contains(command));
846                }
847                latch.countDown();
848            }
849        };
850
851        final MediaController2 controller = createController(mSession.getToken(), true, callback);
852        ControllerInfo controllerInfo = getTestControllerInfo();
853        assertNotNull(controllerInfo);
854
855        mSession.setAllowedCommands(controllerInfo, commands);
856        assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
857    }
858
859    @Test
860    public void testSendCustomCommand() throws InterruptedException {
861        prepareLooper();
862        final SessionCommand2 testCommand = new SessionCommand2(
863                SessionCommand2.COMMAND_CODE_PLAYBACK_PREPARE);
864        final Bundle testArgs = new Bundle();
865        testArgs.putString("args", "testSendCustomAction");
866
867        final CountDownLatch latch = new CountDownLatch(2);
868        final ControllerCallback callback = new ControllerCallback() {
869            @Override
870            public void onCustomCommand(MediaController2 controller, SessionCommand2 command,
871                    Bundle args, ResultReceiver receiver) {
872                assertEquals(testCommand, command);
873                assertTrue(TestUtils.equals(testArgs, args));
874                assertNull(receiver);
875                latch.countDown();
876            }
877        };
878        final MediaController2 controller =
879                createController(mSession.getToken(), true, callback);
880        // TODO(jaewan): Test with multiple controllers
881        mSession.sendCustomCommand(testCommand, testArgs);
882
883        ControllerInfo controllerInfo = getTestControllerInfo();
884        assertNotNull(controllerInfo);
885        // TODO(jaewan): Test receivers as well.
886        mSession.sendCustomCommand(controllerInfo, testCommand, testArgs, null);
887        assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
888    }
889
890    @Test
891    public void testNotifyError() throws InterruptedException {
892        prepareLooper();
893        final int errorCode = MediaSession2.ERROR_CODE_NOT_AVAILABLE_IN_REGION;
894        final Bundle extras = new Bundle();
895        extras.putString("args", "testNotifyError");
896
897        final CountDownLatch latch = new CountDownLatch(1);
898        final ControllerCallback callback = new ControllerCallback() {
899            @Override
900            public void onError(MediaController2 controller, int errorCodeOut, Bundle extrasOut) {
901                assertEquals(errorCode, errorCodeOut);
902                assertTrue(TestUtils.equals(extras, extrasOut));
903                latch.countDown();
904            }
905        };
906        final MediaController2 controller = createController(mSession.getToken(), true, callback);
907        // TODO(jaewan): Test with multiple controllers
908        mSession.notifyError(errorCode, extras);
909        assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
910    }
911
912    @Test
913    public void testNotifyRoutesInfoChanged() throws InterruptedException {
914        prepareLooper();
915        final CountDownLatch latch = new CountDownLatch(1);
916        final ControllerCallback callback = new ControllerCallback() {
917            @Override
918            public void onRoutesInfoChanged(@NonNull MediaController2 controller,
919                    @Nullable List<Bundle> routes) {
920                assertNull(routes);
921                latch.countDown();
922            }
923        };
924        final MediaController2 controller = createController(mSession.getToken(), true, callback);
925        ControllerInfo controllerInfo = getTestControllerInfo();
926        mSession.notifyRoutesInfoChanged(controllerInfo, null);
927        assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
928    }
929
930    private ControllerInfo getTestControllerInfo() {
931        List<ControllerInfo> controllers = mSession.getConnectedControllers();
932        assertNotNull(controllers);
933        for (int i = 0; i < controllers.size(); i++) {
934            if (Process.myUid() == controllers.get(i).getUid()) {
935                return controllers.get(i);
936            }
937        }
938        fail("Failed to get test controller info");
939        return null;
940    }
941
942    public class MockOnConnectCallback extends SessionCallback {
943        @Override
944        public SessionCommandGroup2 onConnect(MediaSession2 session,
945                ControllerInfo controllerInfo) {
946            if (Process.myUid() != controllerInfo.getUid()) {
947                return null;
948            }
949            assertEquals(mContext.getPackageName(), controllerInfo.getPackageName());
950            assertEquals(Process.myUid(), controllerInfo.getUid());
951            assertFalse(controllerInfo.isTrusted());
952            // Reject all
953            return null;
954        }
955    }
956
957    public class MockOnCommandCallback extends SessionCallback {
958        public final ArrayList<SessionCommand2> commands = new ArrayList<>();
959
960        @Override
961        public boolean onCommandRequest(MediaSession2 session, ControllerInfo controllerInfo,
962                SessionCommand2 command) {
963            assertEquals(mContext.getPackageName(), controllerInfo.getPackageName());
964            assertEquals(Process.myUid(), controllerInfo.getUid());
965            assertFalse(controllerInfo.isTrusted());
966            commands.add(command);
967            if (command.getCommandCode() == SessionCommand2.COMMAND_CODE_PLAYBACK_PAUSE) {
968                return false;
969            }
970            return true;
971        }
972    }
973
974    private static void assertMediaItemListEquals(List<MediaItem2> a, List<MediaItem2> b) {
975        if (a == null || b == null) {
976            assertEquals(a, b);
977        }
978        assertEquals(a.size(), b.size());
979
980        for (int i = 0; i < a.size(); i++) {
981            MediaItem2 aItem = a.get(i);
982            MediaItem2 bItem = b.get(i);
983
984            if (aItem == null || bItem == null) {
985                assertEquals(aItem, bItem);
986                continue;
987            }
988
989            assertEquals(aItem.getMediaId(), bItem.getMediaId());
990            assertEquals(aItem.getFlags(), bItem.getFlags());
991            TestUtils.equals(aItem.getMetadata().toBundle(), bItem.getMetadata().toBundle());
992
993            // Note: Here it does not check whether DataSourceDesc are equal,
994            // since there DataSourceDec is not comparable.
995        }
996    }
997}
998