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 org.junit.Assert.assertEquals;
20import static org.junit.Assert.assertFalse;
21import static org.junit.Assert.assertNotEquals;
22import static org.junit.Assert.assertNotNull;
23import static org.junit.Assert.assertNull;
24import static org.junit.Assert.assertTrue;
25import static org.junit.Assert.fail;
26
27import android.app.PendingIntent;
28import android.content.Context;
29import android.content.Intent;
30import android.media.AudioManager;
31import android.net.Uri;
32import android.os.Build;
33import android.os.Bundle;
34import android.os.Handler;
35import android.os.HandlerThread;
36import android.os.Process;
37import android.os.ResultReceiver;
38import android.support.test.filters.FlakyTest;
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.media.MediaController2.ControllerCallback;
45import androidx.media.MediaController2.PlaybackInfo;
46import androidx.media.MediaLibraryService2.MediaLibrarySession.MediaLibrarySessionCallback;
47import androidx.media.MediaSession2.ControllerInfo;
48import androidx.media.MediaSession2.SessionCallback;
49import androidx.media.TestServiceRegistry.SessionServiceCallback;
50import androidx.media.TestUtils.SyncHandler;
51import androidx.testutils.PollingCheck;
52
53import org.junit.After;
54import org.junit.Before;
55import org.junit.Test;
56import org.junit.runner.RunWith;
57
58import java.lang.reflect.Method;
59import java.util.List;
60import java.util.concurrent.CountDownLatch;
61import java.util.concurrent.TimeUnit;
62import java.util.concurrent.atomic.AtomicReference;
63
64/**
65 * Tests {@link MediaController2}.
66 */
67// TODO(jaewan): Implement host-side test so controller and session can run in different processes.
68// TODO(jaewan): Fix flaky failure -- see MediaController2Impl.getController()
69// TODO(jaeawn): Revisit create/close session in the sHandler. It's no longer necessary.
70@SdkSuppress(minSdkVersion = Build.VERSION_CODES.JELLY_BEAN)
71@RunWith(AndroidJUnit4.class)
72@SmallTest
73@FlakyTest
74public class MediaController2Test extends MediaSession2TestBase {
75    private static final String TAG = "MediaController2Test";
76
77    PendingIntent mIntent;
78    MediaSession2 mSession;
79    MediaController2 mController;
80    MockPlayer mPlayer;
81    MockPlaylistAgent mMockAgent;
82    AudioManager mAudioManager;
83
84    @Before
85    @Override
86    public void setUp() throws Exception {
87        super.setUp();
88        final Intent sessionActivity = new Intent(mContext, MockActivity.class);
89        // Create this test specific MediaSession2 to use our own Handler.
90        mIntent = PendingIntent.getActivity(mContext, 0, sessionActivity, 0);
91
92        mPlayer = new MockPlayer(1);
93        mMockAgent = new MockPlaylistAgent();
94        mSession = new MediaSession2.Builder(mContext)
95                .setPlayer(mPlayer)
96                .setPlaylistAgent(mMockAgent)
97                .setSessionCallback(sHandlerExecutor, new SessionCallback() {
98                    @Override
99                    public SessionCommandGroup2 onConnect(MediaSession2 session,
100                            ControllerInfo controller) {
101                        if (Process.myUid() == controller.getUid()) {
102                            return super.onConnect(session, controller);
103                        }
104                        return null;
105                    }
106
107                    @Override
108                    public void onPlaylistMetadataChanged(MediaSession2 session,
109                            MediaPlaylistAgent playlistAgent,
110                            MediaMetadata2 metadata) {
111                        super.onPlaylistMetadataChanged(session, playlistAgent, metadata);
112                    }
113                })
114                .setSessionActivity(mIntent)
115                .setId(TAG).build();
116        mController = createController(mSession.getToken());
117        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
118        TestServiceRegistry.getInstance().setHandler(sHandler);
119    }
120
121    @After
122    @Override
123    public void cleanUp() throws Exception {
124        super.cleanUp();
125        if (mSession != null) {
126            mSession.close();
127        }
128        TestServiceRegistry.getInstance().cleanUp();
129    }
130
131    /**
132     * Test if the {@link MediaSession2TestBase.TestControllerCallback} wraps the callback proxy
133     * without missing any method.
134     */
135    @Test
136    public void testTestControllerCallback() {
137        prepareLooper();
138        Method[] methods = TestControllerCallback.class.getMethods();
139        assertNotNull(methods);
140        for (int i = 0; i < methods.length; i++) {
141            // For any methods in the controller callback, TestControllerCallback should have
142            // overriden the method and call matching API in the callback proxy.
143            assertNotEquals("TestControllerCallback should override " + methods[i]
144                            + " and call callback proxy",
145                    ControllerCallback.class, methods[i].getDeclaringClass());
146        }
147    }
148
149    @Test
150    public void testPlay() {
151        prepareLooper();
152        mController.play();
153        try {
154            assertTrue(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
155        } catch (InterruptedException e) {
156            fail(e.getMessage());
157        }
158        assertTrue(mPlayer.mPlayCalled);
159    }
160
161    @Test
162    public void testPause() {
163        prepareLooper();
164        mController.pause();
165        try {
166            assertTrue(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
167        } catch (InterruptedException e) {
168            fail(e.getMessage());
169        }
170        assertTrue(mPlayer.mPauseCalled);
171    }
172
173    @Test
174    public void testReset() {
175        prepareLooper();
176        mController.reset();
177        try {
178            assertTrue(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
179        } catch (InterruptedException e) {
180            fail(e.getMessage());
181        }
182        assertTrue(mPlayer.mResetCalled);
183    }
184
185    @Test
186    public void testPrepare() {
187        prepareLooper();
188        mController.prepare();
189        try {
190            assertTrue(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
191        } catch (InterruptedException e) {
192            fail(e.getMessage());
193        }
194        assertTrue(mPlayer.mPrepareCalled);
195    }
196
197    @Test
198    public void testSeekTo() {
199        prepareLooper();
200        final long seekPosition = 12125L;
201        mController.seekTo(seekPosition);
202        try {
203            assertTrue(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
204        } catch (InterruptedException e) {
205            fail(e.getMessage());
206        }
207        assertTrue(mPlayer.mSeekToCalled);
208        assertEquals(seekPosition, mPlayer.mSeekPosition);
209    }
210
211    @Test
212    public void testGettersAfterConnected() throws InterruptedException {
213        prepareLooper();
214        final int state = MediaPlayerInterface.PLAYER_STATE_PLAYING;
215        final int bufferingState = MediaPlayerInterface.BUFFERING_STATE_BUFFERING_COMPLETE;
216        final long position = 150000;
217        final long bufferedPosition = 900000;
218        final float speed = 0.5f;
219        final long timeDiff = 102;
220        final MediaItem2 currentMediaItem = TestUtils.createMediaItemWithMetadata();
221
222        mPlayer.mLastPlayerState = state;
223        mPlayer.mLastBufferingState = bufferingState;
224        mPlayer.mCurrentPosition = position;
225        mPlayer.mBufferedPosition = bufferedPosition;
226        mPlayer.mPlaybackSpeed = speed;
227        mMockAgent.mCurrentMediaItem = currentMediaItem;
228
229        MediaController2 controller = createController(mSession.getToken());
230        controller.setTimeDiff(timeDiff);
231        assertEquals(state, controller.getPlayerState());
232        assertEquals(bufferedPosition, controller.getBufferedPosition());
233        assertEquals(speed, controller.getPlaybackSpeed(), 0.0f);
234        assertEquals(position + (long) (speed * timeDiff), controller.getCurrentPosition());
235        assertEquals(currentMediaItem, controller.getCurrentMediaItem());
236    }
237
238    @Test
239    public void testUpdatePlayer() throws InterruptedException {
240        prepareLooper();
241        final int testState = MediaPlayerInterface.PLAYER_STATE_PLAYING;
242        final List<MediaItem2> testPlaylist = TestUtils.createPlaylist(3);
243        final AudioAttributesCompat testAudioAttributes = new AudioAttributesCompat.Builder()
244                .setLegacyStreamType(AudioManager.STREAM_RING).build();
245        final CountDownLatch latch = new CountDownLatch(3);
246        mController = createController(mSession.getToken(), true, new ControllerCallback() {
247            @Override
248            public void onPlayerStateChanged(MediaController2 controller, int state) {
249                assertEquals(mController, controller);
250                assertEquals(testState, state);
251                latch.countDown();
252            }
253
254            @Override
255            public void onPlaylistChanged(MediaController2 controller, List<MediaItem2> list,
256                    MediaMetadata2 metadata) {
257                assertEquals(mController, controller);
258                assertEquals(testPlaylist, list);
259                assertNull(metadata);
260                latch.countDown();
261            }
262
263            @Override
264            public void onPlaybackInfoChanged(MediaController2 controller, PlaybackInfo info) {
265                assertEquals(mController, controller);
266                assertEquals(testAudioAttributes, info.getAudioAttributes());
267                latch.countDown();
268            }
269        });
270
271        MockPlayer player = new MockPlayer(0);
272        player.mLastPlayerState = testState;
273        player.setAudioAttributes(testAudioAttributes);
274
275        MockPlaylistAgent agent = new MockPlaylistAgent();
276        agent.mPlaylist = testPlaylist;
277
278        mSession.updatePlayer(player, agent, null);
279        assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
280    }
281
282    @Test
283    public void testGetSessionActivity() {
284        prepareLooper();
285        PendingIntent sessionActivity = mController.getSessionActivity();
286        assertEquals(mContext.getPackageName(), sessionActivity.getCreatorPackage());
287        assertEquals(Process.myUid(), sessionActivity.getCreatorUid());
288    }
289
290    @Test
291    public void testSetPlaylist() throws InterruptedException {
292        prepareLooper();
293        final List<MediaItem2> list = TestUtils.createPlaylist(2);
294        mController.setPlaylist(list, null /* Metadata */);
295        assertTrue(mMockAgent.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
296
297        assertTrue(mMockAgent.mSetPlaylistCalled);
298        assertNull(mMockAgent.mMetadata);
299
300        assertNotNull(mMockAgent.mPlaylist);
301        assertEquals(list.size(), mMockAgent.mPlaylist.size());
302        for (int i = 0; i < list.size(); i++) {
303            // MediaController2.setPlaylist does not ensure the equality of the items.
304            assertEquals(list.get(i).getMediaId(), mMockAgent.mPlaylist.get(i).getMediaId());
305        }
306    }
307
308    /**
309     * This also tests {@link ControllerCallback#onPlaylistChanged(
310     * MediaController2, List, MediaMetadata2)}.
311     */
312    @Test
313    public void testGetPlaylist() throws InterruptedException {
314        prepareLooper();
315        final List<MediaItem2> testList = TestUtils.createPlaylist(2);
316        final AtomicReference<List<MediaItem2>> listFromCallback = new AtomicReference<>();
317        final CountDownLatch latch = new CountDownLatch(1);
318        final ControllerCallback callback = new ControllerCallback() {
319            @Override
320            public void onPlaylistChanged(MediaController2 controller,
321                    List<MediaItem2> playlist, MediaMetadata2 metadata) {
322                assertNotNull(playlist);
323                assertEquals(testList.size(), playlist.size());
324                for (int i = 0; i < playlist.size(); i++) {
325                    assertEquals(testList.get(i).getMediaId(), playlist.get(i).getMediaId());
326                }
327                listFromCallback.set(playlist);
328                latch.countDown();
329            }
330        };
331        final MediaPlaylistAgent agent = new MockPlaylistAgent() {
332            @Override
333            public List<MediaItem2> getPlaylist() {
334                return testList;
335            }
336        };
337        try (MediaSession2 session = new MediaSession2.Builder(mContext)
338                .setPlayer(mPlayer)
339                .setId("testControllerCallback_onPlaylistChanged")
340                .setSessionCallback(sHandlerExecutor, new SessionCallback() {})
341                .setPlaylistAgent(agent)
342                .build()) {
343            MediaController2 controller = createController(
344                    session.getToken(), true, callback);
345            agent.notifyPlaylistChanged();
346            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
347            assertEquals(listFromCallback.get(), controller.getPlaylist());
348        }
349    }
350
351    @Test
352    public void testUpdatePlaylistMetadata() throws InterruptedException {
353        prepareLooper();
354        final MediaMetadata2 testMetadata = TestUtils.createMetadata();
355        mController.updatePlaylistMetadata(testMetadata);
356        assertTrue(mMockAgent.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
357
358        assertTrue(mMockAgent.mUpdatePlaylistMetadataCalled);
359        assertNotNull(mMockAgent.mMetadata);
360        assertEquals(testMetadata.getMediaId(), mMockAgent.mMetadata.getMediaId());
361    }
362
363    @Test
364    public void testGetPlaylistMetadata() throws InterruptedException {
365        prepareLooper();
366        final MediaMetadata2 testMetadata = TestUtils.createMetadata();
367        final AtomicReference<MediaMetadata2> metadataFromCallback = new AtomicReference<>();
368        final CountDownLatch latch = new CountDownLatch(1);
369        final ControllerCallback callback = new ControllerCallback() {
370            @Override
371            public void onPlaylistMetadataChanged(MediaController2 controller,
372                    MediaMetadata2 metadata) {
373                assertNotNull(testMetadata);
374                assertEquals(testMetadata.getMediaId(), metadata.getMediaId());
375                metadataFromCallback.set(metadata);
376                latch.countDown();
377            }
378        };
379        final MediaPlaylistAgent agent = new MockPlaylistAgent() {
380            @Override
381            public MediaMetadata2 getPlaylistMetadata() {
382                return testMetadata;
383            }
384        };
385        try (MediaSession2 session = new MediaSession2.Builder(mContext)
386                .setPlayer(mPlayer)
387                .setId("testGetPlaylistMetadata")
388                .setSessionCallback(sHandlerExecutor, new SessionCallback() {})
389                .setPlaylistAgent(agent)
390                .build()) {
391            MediaController2 controller = createController(session.getToken(), true, callback);
392            agent.notifyPlaylistMetadataChanged();
393            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
394            assertEquals(metadataFromCallback.get().getMediaId(),
395                    controller.getPlaylistMetadata().getMediaId());
396        }
397    }
398
399    @Test
400    public void testSetPlaybackSpeed() throws Exception {
401        prepareLooper();
402        final float speed = 1.5f;
403        mController.setPlaybackSpeed(speed);
404        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
405        assertEquals(speed, mPlayer.mPlaybackSpeed, 0.0f);
406    }
407
408    /**
409     * Test whether {@link MediaSession2#setPlaylist(List, MediaMetadata2)} is notified
410     * through the
411     * {@link ControllerCallback#onPlaylistMetadataChanged(MediaController2, MediaMetadata2)}
412     * if the controller doesn't have {@link SessionCommand2#COMMAND_CODE_PLAYLIST_GET_LIST} but
413     * {@link SessionCommand2#COMMAND_CODE_PLAYLIST_GET_LIST_METADATA}.
414     */
415    @Test
416    public void testControllerCallback_onPlaylistMetadataChanged() throws InterruptedException {
417        prepareLooper();
418        final MediaItem2 item = TestUtils.createMediaItemWithMetadata();
419        final List<MediaItem2> list = TestUtils.createPlaylist(2);
420        final CountDownLatch latch = new CountDownLatch(1);
421        final ControllerCallback callback = new ControllerCallback() {
422            @Override
423            public void onPlaylistMetadataChanged(MediaController2 controller,
424                    MediaMetadata2 metadata) {
425                assertNotNull(metadata);
426                assertEquals(item.getMediaId(), metadata.getMediaId());
427                latch.countDown();
428            }
429        };
430        final SessionCallback sessionCallback = new SessionCallback() {
431            @Override
432            public SessionCommandGroup2 onConnect(MediaSession2 session,
433                    ControllerInfo controller) {
434                if (Process.myUid() == controller.getUid()) {
435                    SessionCommandGroup2 commands = new SessionCommandGroup2();
436                    commands.addCommand(new SessionCommand2(
437                              SessionCommand2.COMMAND_CODE_PLAYLIST_GET_LIST_METADATA));
438                    return commands;
439                }
440                return super.onConnect(session, controller);
441            }
442        };
443        final MediaPlaylistAgent agent = new MockPlaylistAgent() {
444            @Override
445            public MediaMetadata2 getPlaylistMetadata() {
446                return item.getMetadata();
447            }
448
449            @Override
450            public List<MediaItem2> getPlaylist() {
451                return list;
452            }
453        };
454        try (MediaSession2 session = new MediaSession2.Builder(mContext)
455                .setPlayer(mPlayer)
456                .setId("testControllerCallback_onPlaylistMetadataChanged")
457                .setSessionCallback(sHandlerExecutor, sessionCallback)
458                .setPlaylistAgent(agent)
459                .build()) {
460            MediaController2 controller = createController(session.getToken(), true, callback);
461            agent.notifyPlaylistMetadataChanged();
462            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
463        }
464    }
465
466
467    @Test
468    public void testControllerCallback_onSeekCompleted() throws InterruptedException {
469        prepareLooper();
470        final long testSeekPosition = 400;
471        final long testPosition = 500;
472        final CountDownLatch latch = new CountDownLatch(1);
473        final ControllerCallback callback = new ControllerCallback() {
474            @Override
475            public void onSeekCompleted(MediaController2 controller, long position) {
476                controller.setTimeDiff(Long.valueOf(0));
477                assertEquals(testSeekPosition, position);
478                assertEquals(testPosition, controller.getCurrentPosition());
479                latch.countDown();
480            }
481        };
482        final MediaController2 controller = createController(mSession.getToken(), true, callback);
483        mPlayer.mCurrentPosition = testPosition;
484        mPlayer.notifySeekCompleted(testSeekPosition);
485        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
486    }
487
488    @Test
489    public void testControllerCallback_onBufferingStateChanged() throws InterruptedException {
490        prepareLooper();
491        final List<MediaItem2> testPlaylist = TestUtils.createPlaylist(3);
492        final MediaItem2 testItem = testPlaylist.get(0);
493        final int testBufferingState = MediaPlayerInterface.BUFFERING_STATE_BUFFERING_AND_PLAYABLE;
494        final long testBufferingPosition = 500;
495        final CountDownLatch latch = new CountDownLatch(1);
496        final ControllerCallback callback = new ControllerCallback() {
497            @Override
498            public void onBufferingStateChanged(MediaController2 controller, MediaItem2 item,
499                    int state) {
500                controller.setTimeDiff(Long.valueOf(0));
501                assertEquals(testItem, item);
502                assertEquals(testBufferingState, state);
503                assertEquals(testBufferingState, controller.getBufferingState());
504                assertEquals(testBufferingPosition, controller.getBufferedPosition());
505                latch.countDown();
506            }
507        };
508        final MediaController2 controller = createController(mSession.getToken(), true, callback);
509        mSession.setPlaylist(testPlaylist, null);
510        mPlayer.mBufferedPosition = testBufferingPosition;
511        mPlayer.notifyBufferingStateChanged(testItem.getDataSourceDesc(), testBufferingState);
512        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
513    }
514
515    @Test
516    public void testControllerCallback_onPlayerStateChanged() throws InterruptedException {
517        prepareLooper();
518        final int testPlayerState = MediaPlayerInterface.PLAYER_STATE_PLAYING;
519        final long testPosition = 500;
520        final CountDownLatch latch = new CountDownLatch(1);
521        final ControllerCallback callback = new ControllerCallback() {
522            @Override
523            public void onPlayerStateChanged(MediaController2 controller, int state) {
524                controller.setTimeDiff(Long.valueOf(0));
525                assertEquals(testPlayerState, state);
526                assertEquals(testPlayerState, controller.getPlayerState());
527                assertEquals(testPosition, controller.getCurrentPosition());
528                latch.countDown();
529            }
530        };
531        final MediaController2 controller = createController(mSession.getToken(), true, callback);
532        mPlayer.mCurrentPosition = testPosition;
533        mPlayer.notifyPlaybackState(testPlayerState);
534        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
535    }
536
537    @Test
538    public void testAddPlaylistItem() throws InterruptedException {
539        prepareLooper();
540        final int testIndex = 12;
541        final MediaItem2 testMediaItem = TestUtils.createMediaItemWithMetadata();
542        mController.addPlaylistItem(testIndex, testMediaItem);
543        assertTrue(mMockAgent.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
544
545        assertTrue(mMockAgent.mAddPlaylistItemCalled);
546        assertEquals(testIndex, mMockAgent.mIndex);
547        // MediaController2.addPlaylistItem does not ensure the equality of the items.
548        assertEquals(testMediaItem.getMediaId(), mMockAgent.mItem.getMediaId());
549    }
550
551    @Test
552    public void testRemovePlaylistItem() throws InterruptedException {
553        prepareLooper();
554        mMockAgent.mPlaylist = TestUtils.createPlaylist(2);
555
556        // Recreate controller for sending removePlaylistItem.
557        // It's easier to ensure that MediaController2.getPlaylist() returns the playlist from the
558        // agent.
559        MediaController2 controller = createController(mSession.getToken());
560        MediaItem2 targetItem = controller.getPlaylist().get(0);
561        controller.removePlaylistItem(targetItem);
562        assertTrue(mMockAgent.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
563
564        assertTrue(mMockAgent.mRemovePlaylistItemCalled);
565        assertEquals(targetItem, mMockAgent.mItem);
566    }
567
568    @Test
569    public void testReplacePlaylistItem() throws InterruptedException {
570        prepareLooper();
571        final int testIndex = 12;
572        final MediaItem2 testMediaItem = TestUtils.createMediaItemWithMetadata();
573        mController.replacePlaylistItem(testIndex, testMediaItem);
574        assertTrue(mMockAgent.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
575
576        assertTrue(mMockAgent.mReplacePlaylistItemCalled);
577        // MediaController2.replacePlaylistItem does not ensure the equality of the items.
578        assertEquals(testMediaItem.getMediaId(), mMockAgent.mItem.getMediaId());
579    }
580
581    @Test
582    public void testSkipToPreviousItem() throws InterruptedException {
583        prepareLooper();
584        mController.skipToPreviousItem();
585        assertTrue(mMockAgent.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
586        assertTrue(mMockAgent.mSkipToPreviousItemCalled);
587    }
588
589    @Test
590    public void testSkipToNextItem() throws InterruptedException {
591        prepareLooper();
592        mController.skipToNextItem();
593        assertTrue(mMockAgent.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
594        assertTrue(mMockAgent.mSkipToNextItemCalled);
595    }
596
597    @Test
598    public void testSkipToPlaylistItem() throws InterruptedException {
599        prepareLooper();
600        MediaController2 controller = createController(mSession.getToken());
601        MediaItem2 targetItem = TestUtils.createMediaItemWithMetadata();
602        controller.skipToPlaylistItem(targetItem);
603        assertTrue(mMockAgent.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
604
605        assertTrue(mMockAgent.mSkipToPlaylistItemCalled);
606        assertEquals(targetItem, mMockAgent.mItem);
607    }
608
609    /**
610     * This also tests {@link ControllerCallback#onShuffleModeChanged(MediaController2, int)}.
611     */
612    @Test
613    public void testGetShuffleMode() throws InterruptedException {
614        prepareLooper();
615        final int testShuffleMode = MediaPlaylistAgent.SHUFFLE_MODE_GROUP;
616        final MediaPlaylistAgent agent = new MockPlaylistAgent() {
617            @Override
618            public int getShuffleMode() {
619                return testShuffleMode;
620            }
621        };
622        final CountDownLatch latch = new CountDownLatch(1);
623        final ControllerCallback callback = new ControllerCallback() {
624            @Override
625            public void onShuffleModeChanged(MediaController2 controller, int shuffleMode) {
626                assertEquals(testShuffleMode, shuffleMode);
627                latch.countDown();
628            }
629        };
630        mSession.updatePlayer(mPlayer, agent, null);
631        MediaController2 controller = createController(mSession.getToken(), true, callback);
632        agent.notifyShuffleModeChanged();
633        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
634        assertEquals(testShuffleMode, controller.getShuffleMode());
635    }
636
637    @Test
638    public void testSetShuffleMode() throws InterruptedException {
639        prepareLooper();
640        final int testShuffleMode = MediaPlaylistAgent.SHUFFLE_MODE_GROUP;
641        mController.setShuffleMode(testShuffleMode);
642        assertTrue(mMockAgent.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
643
644        assertTrue(mMockAgent.mSetShuffleModeCalled);
645        assertEquals(testShuffleMode, mMockAgent.mShuffleMode);
646    }
647
648    /**
649     * This also tests {@link ControllerCallback#onRepeatModeChanged(MediaController2, int)}.
650     */
651    @Test
652    public void testGetRepeatMode() throws InterruptedException {
653        prepareLooper();
654        final int testRepeatMode = MediaPlaylistAgent.REPEAT_MODE_GROUP;
655        final MediaPlaylistAgent agent = new MockPlaylistAgent() {
656            @Override
657            public int getRepeatMode() {
658                return testRepeatMode;
659            }
660        };
661        final CountDownLatch latch = new CountDownLatch(1);
662        final ControllerCallback callback = new ControllerCallback() {
663            @Override
664            public void onRepeatModeChanged(MediaController2 controller, int repeatMode) {
665                assertEquals(testRepeatMode, repeatMode);
666                latch.countDown();
667            }
668        };
669        mSession.updatePlayer(mPlayer, agent, null);
670        MediaController2 controller = createController(mSession.getToken(), true, callback);
671        agent.notifyRepeatModeChanged();
672        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
673        assertEquals(testRepeatMode, controller.getRepeatMode());
674    }
675
676    @Test
677    public void testSetRepeatMode() throws InterruptedException {
678        prepareLooper();
679        final int testRepeatMode = MediaPlaylistAgent.REPEAT_MODE_GROUP;
680        mController.setRepeatMode(testRepeatMode);
681        assertTrue(mMockAgent.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
682
683        assertTrue(mMockAgent.mSetRepeatModeCalled);
684        assertEquals(testRepeatMode, mMockAgent.mRepeatMode);
685    }
686
687    @Test
688    public void testSetVolumeTo() throws Exception {
689        prepareLooper();
690        final int maxVolume = 100;
691        final int currentVolume = 23;
692        final int volumeControlType = VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE;
693        TestVolumeProvider volumeProvider =
694                new TestVolumeProvider(volumeControlType, maxVolume, currentVolume);
695
696        mSession.updatePlayer(new MockPlayer(0), null, volumeProvider);
697        final MediaController2 controller = createController(mSession.getToken(), true, null);
698
699        final int targetVolume = 50;
700        controller.setVolumeTo(targetVolume, 0 /* flags */);
701        assertTrue(volumeProvider.mLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
702        assertTrue(volumeProvider.mSetVolumeToCalled);
703        assertEquals(targetVolume, volumeProvider.mVolume);
704    }
705
706    @Test
707    public void testAdjustVolume() throws Exception {
708        prepareLooper();
709        final int maxVolume = 100;
710        final int currentVolume = 23;
711        final int volumeControlType = VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE;
712        TestVolumeProvider volumeProvider =
713                new TestVolumeProvider(volumeControlType, maxVolume, currentVolume);
714
715        mSession.updatePlayer(new MockPlayer(0), null, volumeProvider);
716        final MediaController2 controller = createController(mSession.getToken(), true, null);
717
718        final int direction = AudioManager.ADJUST_RAISE;
719        controller.adjustVolume(direction, 0 /* flags */);
720        assertTrue(volumeProvider.mLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
721        assertTrue(volumeProvider.mAdjustVolumeCalled);
722        assertEquals(direction, volumeProvider.mDirection);
723    }
724
725    @Test
726    public void testSetVolumeWithLocalVolume() throws Exception {
727        prepareLooper();
728        if (Build.VERSION.SDK_INT >= 21 && mAudioManager.isVolumeFixed()) {
729            // This test is not eligible for this device.
730            return;
731        }
732
733        // Here, we intentionally choose STREAM_ALARM in order not to consider
734        // 'Do Not Disturb' or 'Volume limit'.
735        final int stream = AudioManager.STREAM_ALARM;
736        final int maxVolume = mAudioManager.getStreamMaxVolume(stream);
737        final int minVolume = 0;
738        if (maxVolume <= minVolume) {
739            return;
740        }
741
742        // Set stream of the session.
743        AudioAttributesCompat attrs = new AudioAttributesCompat.Builder()
744                .setLegacyStreamType(stream)
745                .build();
746        mPlayer.setAudioAttributes(attrs);
747        mSession.updatePlayer(mPlayer, null, null);
748
749        final int originalVolume = mAudioManager.getStreamVolume(stream);
750        final int targetVolume = originalVolume == minVolume
751                ? originalVolume + 1 : originalVolume - 1;
752
753        mController.setVolumeTo(targetVolume, AudioManager.FLAG_SHOW_UI);
754        new PollingCheck(WAIT_TIME_MS) {
755            @Override
756            protected boolean check() {
757                return targetVolume == mAudioManager.getStreamVolume(stream);
758            }
759        }.run();
760
761        // Set back to original volume.
762        mAudioManager.setStreamVolume(stream, originalVolume, 0 /* flags */);
763    }
764
765    @Test
766    public void testAdjustVolumeWithLocalVolume() throws Exception {
767        prepareLooper();
768        if (Build.VERSION.SDK_INT >= 21 && mAudioManager.isVolumeFixed()) {
769            // This test is not eligible for this device.
770            return;
771        }
772
773        // Here, we intentionally choose STREAM_ALARM in order not to consider
774        // 'Do Not Disturb' or 'Volume limit'.
775        final int stream = AudioManager.STREAM_ALARM;
776        final int maxVolume = mAudioManager.getStreamMaxVolume(stream);
777        final int minVolume = 0;
778        if (maxVolume <= minVolume) {
779            return;
780        }
781
782        // Set stream of the session.
783        AudioAttributesCompat attrs = new AudioAttributesCompat.Builder()
784                .setLegacyStreamType(stream)
785                .build();
786        mPlayer.setAudioAttributes(attrs);
787        mSession.updatePlayer(mPlayer, null, null);
788
789        final int originalVolume = mAudioManager.getStreamVolume(stream);
790        final int direction = originalVolume == minVolume
791                ? AudioManager.ADJUST_RAISE : AudioManager.ADJUST_LOWER;
792        final int targetVolume = originalVolume + direction;
793
794        mController.adjustVolume(direction, AudioManager.FLAG_SHOW_UI);
795        new PollingCheck(WAIT_TIME_MS) {
796            @Override
797            protected boolean check() {
798                return targetVolume == mAudioManager.getStreamVolume(stream);
799            }
800        }.run();
801
802        // Set back to original volume.
803        mAudioManager.setStreamVolume(stream, originalVolume, 0 /* flags */);
804    }
805
806    @Test
807    public void testGetPackageName() {
808        prepareLooper();
809        assertEquals(mContext.getPackageName(), mController.getSessionToken().getPackageName());
810    }
811
812    @Test
813    public void testSendCustomCommand() throws InterruptedException {
814        prepareLooper();
815        // TODO(jaewan): Need to revisit with the permission.
816        final SessionCommand2 testCommand =
817                new SessionCommand2(SessionCommand2.COMMAND_CODE_PLAYBACK_PREPARE);
818        final Bundle testArgs = new Bundle();
819        testArgs.putString("args", "testSendCustomCommand");
820
821        final CountDownLatch latch = new CountDownLatch(1);
822        final SessionCallback callback = new SessionCallback() {
823            @Override
824            public void onCustomCommand(MediaSession2 session, ControllerInfo controller,
825                    SessionCommand2 customCommand, Bundle args, ResultReceiver cb) {
826                assertEquals(mContext.getPackageName(), controller.getPackageName());
827                assertEquals(testCommand, customCommand);
828                assertTrue(TestUtils.equals(testArgs, args));
829                assertNull(cb);
830                latch.countDown();
831            }
832        };
833        mSession.close();
834        mSession = new MediaSession2.Builder(mContext).setPlayer(mPlayer)
835                .setSessionCallback(sHandlerExecutor, callback).setId(TAG).build();
836        final MediaController2 controller = createController(mSession.getToken());
837        controller.sendCustomCommand(testCommand, testArgs, null);
838        assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
839    }
840
841    @Test
842    public void testControllerCallback_onConnected() throws InterruptedException {
843        prepareLooper();
844        // createController() uses controller callback to wait until the controller becomes
845        // available.
846        MediaController2 controller = createController(mSession.getToken());
847        assertNotNull(controller);
848    }
849
850    @Test
851    public void testControllerCallback_sessionRejects() throws InterruptedException {
852        prepareLooper();
853        final MediaSession2.SessionCallback sessionCallback = new SessionCallback() {
854            @Override
855            public SessionCommandGroup2 onConnect(MediaSession2 session,
856                    ControllerInfo controller) {
857                return null;
858            }
859        };
860        sHandler.postAndSync(new Runnable() {
861            @Override
862            public void run() {
863                mSession.close();
864                mSession = new MediaSession2.Builder(mContext).setPlayer(mPlayer)
865                        .setSessionCallback(sHandlerExecutor, sessionCallback).build();
866            }
867        });
868        MediaController2 controller =
869                createController(mSession.getToken(), false, null);
870        assertNotNull(controller);
871        waitForConnect(controller, false);
872        waitForDisconnect(controller, true);
873    }
874
875    @Test
876    public void testControllerCallback_releaseSession() throws InterruptedException {
877        prepareLooper();
878        mSession.close();
879        waitForDisconnect(mController, true);
880    }
881
882    @Test
883    public void testControllerCallback_close() throws InterruptedException {
884        prepareLooper();
885        mController.close();
886        waitForDisconnect(mController, true);
887    }
888
889    @Test
890    public void testFastForward() throws InterruptedException {
891        prepareLooper();
892        final CountDownLatch latch = new CountDownLatch(1);
893        final SessionCallback callback = new SessionCallback() {
894            @Override
895            public void onFastForward(MediaSession2 session, ControllerInfo controller) {
896                assertEquals(mContext.getPackageName(), controller.getPackageName());
897                latch.countDown();
898            }
899        };
900        try (MediaSession2 session = new MediaSession2.Builder(mContext)
901                .setPlayer(mPlayer)
902                .setSessionCallback(sHandlerExecutor, callback)
903                .setId("testFastForward").build()) {
904            MediaController2 controller = createController(session.getToken());
905            controller.fastForward();
906            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
907        }
908    }
909
910    @Test
911    public void testRewind() throws InterruptedException {
912        prepareLooper();
913        final CountDownLatch latch = new CountDownLatch(1);
914        final SessionCallback callback = new SessionCallback() {
915            @Override
916            public void onRewind(MediaSession2 session, ControllerInfo controller) {
917                assertEquals(mContext.getPackageName(), controller.getPackageName());
918                latch.countDown();
919            }
920        };
921        try (MediaSession2 session = new MediaSession2.Builder(mContext)
922                .setPlayer(mPlayer)
923                .setSessionCallback(sHandlerExecutor, callback)
924                .setId("testRewind").build()) {
925            MediaController2 controller = createController(session.getToken());
926            controller.rewind();
927            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
928        }
929    }
930
931    @Test
932    public void testPlayFromSearch() throws InterruptedException {
933        prepareLooper();
934        final String request = "random query";
935        final Bundle bundle = new Bundle();
936        bundle.putString("key", "value");
937        final CountDownLatch latch = new CountDownLatch(1);
938        final SessionCallback callback = new SessionCallback() {
939            @Override
940            public void onPlayFromSearch(MediaSession2 session, ControllerInfo controller,
941                    String query, Bundle extras) {
942                super.onPlayFromSearch(session, controller, query, extras);
943                assertEquals(mContext.getPackageName(), controller.getPackageName());
944                assertEquals(request, query);
945                assertTrue(TestUtils.equals(bundle, extras));
946                latch.countDown();
947            }
948        };
949        try (MediaSession2 session = new MediaSession2.Builder(mContext)
950                .setPlayer(mPlayer)
951                .setSessionCallback(sHandlerExecutor, callback)
952                .setId("testPlayFromSearch").build()) {
953            MediaController2 controller = createController(session.getToken());
954            controller.playFromSearch(request, bundle);
955            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
956        }
957    }
958
959    @Test
960    public void testPlayFromUri() throws InterruptedException {
961        prepareLooper();
962        final Uri request = Uri.parse("foo://boo");
963        final Bundle bundle = new Bundle();
964        bundle.putString("key", "value");
965        final CountDownLatch latch = new CountDownLatch(1);
966        final SessionCallback callback = new SessionCallback() {
967            @Override
968            public void onPlayFromUri(MediaSession2 session, ControllerInfo controller, Uri uri,
969                    Bundle extras) {
970                assertEquals(mContext.getPackageName(), controller.getPackageName());
971                assertEquals(request, uri);
972                assertTrue(TestUtils.equals(bundle, extras));
973                latch.countDown();
974            }
975        };
976        try (MediaSession2 session = new MediaSession2.Builder(mContext)
977                .setPlayer(mPlayer)
978                .setSessionCallback(sHandlerExecutor, callback)
979                .setId("testPlayFromUri").build()) {
980            MediaController2 controller = createController(session.getToken());
981            controller.playFromUri(request, bundle);
982            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
983        }
984    }
985
986    @Test
987    public void testPlayFromMediaId() throws InterruptedException {
988        prepareLooper();
989        final String request = "media_id";
990        final Bundle bundle = new Bundle();
991        bundle.putString("key", "value");
992        final CountDownLatch latch = new CountDownLatch(1);
993        final SessionCallback callback = new SessionCallback() {
994            @Override
995            public void onPlayFromMediaId(MediaSession2 session, ControllerInfo controller,
996                    String mediaId, Bundle extras) {
997                assertEquals(mContext.getPackageName(), controller.getPackageName());
998                assertEquals(request, mediaId);
999                assertTrue(TestUtils.equals(bundle, extras));
1000                latch.countDown();
1001            }
1002        };
1003        try (MediaSession2 session = new MediaSession2.Builder(mContext)
1004                .setPlayer(mPlayer)
1005                .setSessionCallback(sHandlerExecutor, callback)
1006                .setId("testPlayFromMediaId").build()) {
1007            MediaController2 controller = createController(session.getToken());
1008            controller.playFromMediaId(request, bundle);
1009            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
1010        }
1011    }
1012
1013    @Test
1014    public void testPrepareFromSearch() throws InterruptedException {
1015        prepareLooper();
1016        final String request = "random query";
1017        final Bundle bundle = new Bundle();
1018        bundle.putString("key", "value");
1019        final CountDownLatch latch = new CountDownLatch(1);
1020        final SessionCallback callback = new SessionCallback() {
1021            @Override
1022            public void onPrepareFromSearch(MediaSession2 session, ControllerInfo controller,
1023                    String query, Bundle extras) {
1024                assertEquals(mContext.getPackageName(), controller.getPackageName());
1025                assertEquals(request, query);
1026                assertTrue(TestUtils.equals(bundle, extras));
1027                latch.countDown();
1028            }
1029        };
1030        try (MediaSession2 session = new MediaSession2.Builder(mContext)
1031                .setPlayer(mPlayer)
1032                .setSessionCallback(sHandlerExecutor, callback)
1033                .setId("testPrepareFromSearch").build()) {
1034            MediaController2 controller = createController(session.getToken());
1035            controller.prepareFromSearch(request, bundle);
1036            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
1037        }
1038    }
1039
1040    @Test
1041    public void testPrepareFromUri() throws InterruptedException {
1042        prepareLooper();
1043        final Uri request = Uri.parse("foo://boo");
1044        final Bundle bundle = new Bundle();
1045        bundle.putString("key", "value");
1046        final CountDownLatch latch = new CountDownLatch(1);
1047        final SessionCallback callback = new SessionCallback() {
1048            @Override
1049            public void onPrepareFromUri(MediaSession2 session, ControllerInfo controller, Uri uri,
1050                    Bundle extras) {
1051                assertEquals(mContext.getPackageName(), controller.getPackageName());
1052                assertEquals(request, uri);
1053                assertTrue(TestUtils.equals(bundle, extras));
1054                latch.countDown();
1055            }
1056        };
1057        try (MediaSession2 session = new MediaSession2.Builder(mContext)
1058                .setPlayer(mPlayer)
1059                .setSessionCallback(sHandlerExecutor, callback)
1060                .setId("testPrepareFromUri").build()) {
1061            MediaController2 controller = createController(session.getToken());
1062            controller.prepareFromUri(request, bundle);
1063            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
1064        }
1065    }
1066
1067    @Test
1068    public void testPrepareFromMediaId() throws InterruptedException {
1069        prepareLooper();
1070        final String request = "media_id";
1071        final Bundle bundle = new Bundle();
1072        bundle.putString("key", "value");
1073        final CountDownLatch latch = new CountDownLatch(1);
1074        final SessionCallback callback = new SessionCallback() {
1075            @Override
1076            public void onPrepareFromMediaId(MediaSession2 session, ControllerInfo controller,
1077                    String mediaId, Bundle extras) {
1078                assertEquals(mContext.getPackageName(), controller.getPackageName());
1079                assertEquals(request, mediaId);
1080                assertTrue(TestUtils.equals(bundle, extras));
1081                latch.countDown();
1082            }
1083        };
1084        try (MediaSession2 session = new MediaSession2.Builder(mContext)
1085                .setPlayer(mPlayer)
1086                .setSessionCallback(sHandlerExecutor, callback)
1087                .setId("testPrepareFromMediaId").build()) {
1088            MediaController2 controller = createController(session.getToken());
1089            controller.prepareFromMediaId(request, bundle);
1090            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
1091        }
1092    }
1093
1094    @Test
1095    public void testSetRating() throws InterruptedException {
1096        prepareLooper();
1097        final int ratingType = Rating2.RATING_5_STARS;
1098        final float ratingValue = 3.5f;
1099        final Rating2 rating = Rating2.newStarRating(ratingType, ratingValue);
1100        final String mediaId = "media_id";
1101
1102        final CountDownLatch latch = new CountDownLatch(1);
1103        final SessionCallback callback = new SessionCallback() {
1104            @Override
1105            public void onSetRating(MediaSession2 session, ControllerInfo controller,
1106                    String mediaIdOut, Rating2 ratingOut) {
1107                assertEquals(mContext.getPackageName(), controller.getPackageName());
1108                assertEquals(mediaId, mediaIdOut);
1109                assertEquals(rating, ratingOut);
1110                latch.countDown();
1111            }
1112        };
1113
1114        try (MediaSession2 session = new MediaSession2.Builder(mContext)
1115                .setPlayer(mPlayer)
1116                .setSessionCallback(sHandlerExecutor, callback)
1117                .setId("testSetRating").build()) {
1118            MediaController2 controller = createController(session.getToken());
1119            controller.setRating(mediaId, rating);
1120            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
1121        }
1122    }
1123
1124    @Test
1125    public void testIsConnected() throws InterruptedException {
1126        prepareLooper();
1127        assertTrue(mController.isConnected());
1128        sHandler.postAndSync(new Runnable() {
1129            @Override
1130            public void run() {
1131                mSession.close();
1132            }
1133        });
1134        waitForDisconnect(mController, true);
1135        assertFalse(mController.isConnected());
1136    }
1137
1138    /**
1139     * Test potential deadlock for calls between controller and session.
1140     */
1141    @Test
1142    public void testDeadlock() throws InterruptedException {
1143        prepareLooper();
1144        sHandler.postAndSync(new Runnable() {
1145            @Override
1146            public void run() {
1147                mSession.close();
1148                mSession = null;
1149            }
1150        });
1151
1152        // Two more threads are needed not to block test thread nor test wide thread (sHandler).
1153        final HandlerThread sessionThread = new HandlerThread("testDeadlock_session");
1154        final HandlerThread testThread = new HandlerThread("testDeadlock_test");
1155        sessionThread.start();
1156        testThread.start();
1157        final SyncHandler sessionHandler = new SyncHandler(sessionThread.getLooper());
1158        final Handler testHandler = new Handler(testThread.getLooper());
1159        final CountDownLatch latch = new CountDownLatch(1);
1160        try {
1161            final MockPlayer player = new MockPlayer(0);
1162            sessionHandler.postAndSync(new Runnable() {
1163                @Override
1164                public void run() {
1165                    mSession = new MediaSession2.Builder(mContext)
1166                            .setPlayer(mPlayer)
1167                            .setSessionCallback(sHandlerExecutor, new SessionCallback() {})
1168                            .setId("testDeadlock").build();
1169                }
1170            });
1171            final MediaController2 controller = createController(mSession.getToken());
1172            testHandler.post(new Runnable() {
1173                @Override
1174                public void run() {
1175                    final int state = MediaPlayerInterface.PLAYER_STATE_ERROR;
1176                    for (int i = 0; i < 100; i++) {
1177                        // triggers call from session to controller.
1178                        player.notifyPlaybackState(state);
1179                        // triggers call from controller to session.
1180                        controller.play();
1181
1182                        // Repeat above
1183                        player.notifyPlaybackState(state);
1184                        controller.pause();
1185                        player.notifyPlaybackState(state);
1186                        controller.reset();
1187                        player.notifyPlaybackState(state);
1188                        controller.skipToNextItem();
1189                        player.notifyPlaybackState(state);
1190                        controller.skipToPreviousItem();
1191                    }
1192                    // This may hang if deadlock happens.
1193                    latch.countDown();
1194                }
1195            });
1196            assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
1197        } finally {
1198            if (mSession != null) {
1199                sessionHandler.postAndSync(new Runnable() {
1200                    @Override
1201                    public void run() {
1202                        // Clean up here because sessionHandler will be removed afterwards.
1203                        mSession.close();
1204                        mSession = null;
1205                    }
1206                });
1207            }
1208
1209            if (Build.VERSION.SDK_INT >= 18) {
1210                sessionThread.quitSafely();
1211                testThread.quitSafely();
1212            } else {
1213                sessionThread.quit();
1214                testThread.quit();
1215            }
1216        }
1217    }
1218
1219    @Test
1220    public void testGetServiceToken() {
1221        prepareLooper();
1222        SessionToken2 token = TestUtils.getServiceToken(mContext, MockMediaSessionService2.ID);
1223        assertNotNull(token);
1224        assertEquals(mContext.getPackageName(), token.getPackageName());
1225        assertEquals(MockMediaSessionService2.ID, token.getId());
1226        assertEquals(SessionToken2.TYPE_SESSION_SERVICE, token.getType());
1227    }
1228
1229    @Test
1230    public void testConnectToService_sessionService() throws InterruptedException {
1231        prepareLooper();
1232        testConnectToService(MockMediaSessionService2.ID);
1233    }
1234
1235    @Test
1236    public void testConnectToService_libraryService() throws InterruptedException {
1237        prepareLooper();
1238        testConnectToService(MockMediaLibraryService2.ID);
1239    }
1240
1241    public void testConnectToService(String id) throws InterruptedException {
1242        prepareLooper();
1243        final CountDownLatch latch = new CountDownLatch(1);
1244        final MediaLibrarySessionCallback sessionCallback = new MediaLibrarySessionCallback() {
1245            @Override
1246            public SessionCommandGroup2 onConnect(@NonNull MediaSession2 session,
1247                    @NonNull ControllerInfo controller) {
1248                if (Process.myUid() == controller.getUid()) {
1249                    if (mSession != null) {
1250                        mSession.close();
1251                    }
1252                    mSession = session;
1253                    mPlayer = (MockPlayer) session.getPlayer();
1254                    assertEquals(mContext.getPackageName(), controller.getPackageName());
1255                    assertFalse(controller.isTrusted());
1256                    latch.countDown();
1257                }
1258                return super.onConnect(session, controller);
1259            }
1260        };
1261        TestServiceRegistry.getInstance().setSessionCallback(sessionCallback);
1262
1263        final SessionCommand2 testCommand = new SessionCommand2("testConnectToService", null);
1264        final CountDownLatch controllerLatch = new CountDownLatch(1);
1265        mController = createController(TestUtils.getServiceToken(mContext, id), true,
1266                new ControllerCallback() {
1267                    @Override
1268                    public void onCustomCommand(MediaController2 controller,
1269                            SessionCommand2 command, Bundle args, ResultReceiver receiver) {
1270                        if (testCommand.equals(command)) {
1271                            controllerLatch.countDown();
1272                        }
1273                    }
1274                }
1275        );
1276        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
1277
1278        // Test command from controller to session service.
1279        mController.play();
1280        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
1281        assertTrue(mPlayer.mPlayCalled);
1282
1283        // Test command from session service to controller.
1284        mSession.sendCustomCommand(testCommand, null);
1285        assertTrue(controllerLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
1286    }
1287
1288    @Test
1289    public void testControllerAfterSessionIsGone_session() throws InterruptedException {
1290        prepareLooper();
1291        testControllerAfterSessionIsClosed(mSession.getToken().getId());
1292    }
1293
1294    @Test
1295    public void testControllerAfterSessionIsClosed_sessionService() throws InterruptedException {
1296        prepareLooper();
1297        testConnectToService(MockMediaSessionService2.ID);
1298        testControllerAfterSessionIsClosed(MockMediaSessionService2.ID);
1299    }
1300
1301    @Test
1302    public void testSubscribeRouteInfo() throws InterruptedException {
1303        prepareLooper();
1304        final TestSessionCallback callback = new TestSessionCallback() {
1305            @Override
1306            public void onSubscribeRoutesInfo(@NonNull MediaSession2 session,
1307                    @NonNull ControllerInfo controller) {
1308                assertEquals(mContext.getPackageName(), controller.getPackageName());
1309                mLatch.countDown();
1310            }
1311
1312            @Override
1313            public void onUnsubscribeRoutesInfo(@NonNull MediaSession2 session,
1314                    @NonNull ControllerInfo controller) {
1315                assertEquals(mContext.getPackageName(), controller.getPackageName());
1316                mLatch.countDown();
1317            }
1318        };
1319        mSession.close();
1320        mSession = new MediaSession2.Builder(mContext).setPlayer(mPlayer)
1321                .setSessionCallback(sHandlerExecutor, callback).setId(TAG).build();
1322        final MediaController2 controller = createController(mSession.getToken());
1323
1324        callback.resetLatchCount(1);
1325        controller.subscribeRoutesInfo();
1326        assertTrue(callback.mLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
1327
1328        callback.resetLatchCount(1);
1329        controller.unsubscribeRoutesInfo();
1330        assertTrue(callback.mLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
1331    }
1332
1333    @Test
1334    public void testSelectRouteInfo() throws InterruptedException {
1335        prepareLooper();
1336        final Bundle testRoute = new Bundle();
1337        testRoute.putString("id", "testRoute");
1338        final TestSessionCallback callback = new TestSessionCallback() {
1339            @Override
1340            public void onSelectRoute(@NonNull MediaSession2 session,
1341                    @NonNull ControllerInfo controller, @NonNull Bundle route) {
1342                assertEquals(mContext.getPackageName(), controller.getPackageName());
1343                assertTrue(TestUtils.equals(route, testRoute));
1344                mLatch.countDown();
1345            }
1346        };
1347        mSession.close();
1348        mSession = new MediaSession2.Builder(mContext).setPlayer(mPlayer)
1349                .setSessionCallback(sHandlerExecutor, callback).setId(TAG).build();
1350        final MediaController2 controller = createController(mSession.getToken());
1351
1352        callback.resetLatchCount(1);
1353        controller.selectRoute(testRoute);
1354        assertTrue(callback.mLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
1355    }
1356
1357    @Test
1358    public void testClose_beforeConnected() throws InterruptedException {
1359        prepareLooper();
1360        MediaController2 controller =
1361                createController(mSession.getToken(), false, null);
1362        controller.close();
1363    }
1364
1365    @Test
1366    public void testClose_twice() {
1367        prepareLooper();
1368        mController.close();
1369        mController.close();
1370    }
1371
1372    @Test
1373    public void testClose_session() throws InterruptedException {
1374        prepareLooper();
1375        final String id = mSession.getToken().getId();
1376        mController.close();
1377        // close is done immediately for session.
1378        testNoInteraction();
1379
1380        // Test whether the controller is notified about later close of the session or
1381        // re-creation.
1382        testControllerAfterSessionIsClosed(id);
1383    }
1384
1385    @Test
1386    public void testClose_sessionService() throws InterruptedException {
1387        prepareLooper();
1388        testCloseFromService(MockMediaSessionService2.ID);
1389    }
1390
1391    @Test
1392    public void testClose_libraryService() throws InterruptedException {
1393        prepareLooper();
1394        testCloseFromService(MockMediaLibraryService2.ID);
1395    }
1396
1397    private void testCloseFromService(String id) throws InterruptedException {
1398        final CountDownLatch latch = new CountDownLatch(1);
1399        TestServiceRegistry.getInstance().setSessionServiceCallback(new SessionServiceCallback() {
1400            @Override
1401            public void onCreated() {
1402                // Do nothing.
1403            }
1404
1405            @Override
1406            public void onDestroyed() {
1407                latch.countDown();
1408            }
1409        });
1410        mController = createController(TestUtils.getServiceToken(mContext, id));
1411        mController.close();
1412        // Wait until close triggers onDestroy() of the session service.
1413        assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
1414        assertNull(TestServiceRegistry.getInstance().getServiceInstance());
1415        testNoInteraction();
1416
1417        // Test whether the controller is notified about later close of the session or
1418        // re-creation.
1419        testControllerAfterSessionIsClosed(id);
1420    }
1421
1422    private void testControllerAfterSessionIsClosed(final String id) throws InterruptedException {
1423        // This cause session service to be died.
1424        mSession.close();
1425        waitForDisconnect(mController, true);
1426        testNoInteraction();
1427
1428        // Ensure that the controller cannot use newly create session with the same ID.
1429        // Recreated session has different session stub, so previously created controller
1430        // shouldn't be available.
1431        mSession = new MediaSession2.Builder(mContext)
1432                .setPlayer(mPlayer)
1433                .setSessionCallback(sHandlerExecutor, new SessionCallback() {})
1434                .setId(id).build();
1435        testNoInteraction();
1436    }
1437
1438    // Test that mSession and mController doesn't interact.
1439    // Note that this method can be called after the mSession is died, so mSession may not have
1440    // valid player.
1441    private void testNoInteraction() throws InterruptedException {
1442        // TODO: check that calls from the controller to session shouldn't be delivered.
1443
1444        // Calls from the session to controller shouldn't be delivered.
1445        final CountDownLatch latch = new CountDownLatch(1);
1446        setRunnableForOnCustomCommand(mController, new Runnable() {
1447            @Override
1448            public void run() {
1449                latch.countDown();
1450            }
1451        });
1452        SessionCommand2 customCommand = new SessionCommand2("testNoInteraction", null);
1453        mSession.sendCustomCommand(customCommand, null);
1454        assertFalse(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
1455        setRunnableForOnCustomCommand(mController, null);
1456    }
1457
1458    // TODO(jaewan): Add  test for service connect rejection, when we differentiate session
1459    //               active/inactive and connection accept/refuse
1460
1461    class TestVolumeProvider extends VolumeProviderCompat {
1462        final CountDownLatch mLatch = new CountDownLatch(1);
1463        boolean mSetVolumeToCalled;
1464        boolean mAdjustVolumeCalled;
1465        int mVolume;
1466        int mDirection;
1467
1468        TestVolumeProvider(int controlType, int maxVolume, int currentVolume) {
1469            super(controlType, maxVolume, currentVolume);
1470        }
1471
1472        @Override
1473        public void onSetVolumeTo(int volume) {
1474            mSetVolumeToCalled = true;
1475            mVolume = volume;
1476            mLatch.countDown();
1477        }
1478
1479        @Override
1480        public void onAdjustVolume(int direction) {
1481            mAdjustVolumeCalled = true;
1482            mDirection = direction;
1483            mLatch.countDown();
1484        }
1485    }
1486
1487    class TestSessionCallback extends SessionCallback {
1488        CountDownLatch mLatch;
1489
1490        void resetLatchCount(int count) {
1491            mLatch = new CountDownLatch(count);
1492        }
1493    }
1494}
1495