Home | History | Annotate | Download | only in media
      1 /*
      2  * Copyright 2018 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package androidx.media;
     18 
     19 import static org.junit.Assert.assertEquals;
     20 import static org.junit.Assert.assertFalse;
     21 import static org.junit.Assert.assertNotEquals;
     22 import static org.junit.Assert.assertNotNull;
     23 import static org.junit.Assert.assertNull;
     24 import static org.junit.Assert.assertTrue;
     25 import static org.junit.Assert.fail;
     26 
     27 import android.app.PendingIntent;
     28 import android.content.Context;
     29 import android.content.Intent;
     30 import android.media.AudioManager;
     31 import android.net.Uri;
     32 import android.os.Build;
     33 import android.os.Bundle;
     34 import android.os.Handler;
     35 import android.os.HandlerThread;
     36 import android.os.Process;
     37 import android.os.ResultReceiver;
     38 import android.support.test.filters.FlakyTest;
     39 import android.support.test.filters.SdkSuppress;
     40 import android.support.test.filters.SmallTest;
     41 import android.support.test.runner.AndroidJUnit4;
     42 
     43 import androidx.annotation.NonNull;
     44 import androidx.media.MediaController2.ControllerCallback;
     45 import androidx.media.MediaController2.PlaybackInfo;
     46 import androidx.media.MediaLibraryService2.MediaLibrarySession.MediaLibrarySessionCallback;
     47 import androidx.media.MediaSession2.ControllerInfo;
     48 import androidx.media.MediaSession2.SessionCallback;
     49 import androidx.media.TestServiceRegistry.SessionServiceCallback;
     50 import androidx.media.TestUtils.SyncHandler;
     51 import androidx.testutils.PollingCheck;
     52 
     53 import org.junit.After;
     54 import org.junit.Before;
     55 import org.junit.Test;
     56 import org.junit.runner.RunWith;
     57 
     58 import java.lang.reflect.Method;
     59 import java.util.List;
     60 import java.util.concurrent.CountDownLatch;
     61 import java.util.concurrent.TimeUnit;
     62 import 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
     74 public 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