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