• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2017 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 com.android.bluetooth.avrcp;
18 
19 import static org.mockito.Mockito.*;
20 
21 import android.media.MediaDescription;
22 import android.media.MediaMetadata;
23 import android.media.session.MediaSession;
24 import android.media.session.PlaybackState;
25 import android.os.HandlerThread;
26 import android.os.TestLooperManager;
27 import android.support.test.InstrumentationRegistry;
28 import android.support.test.filters.SmallTest;
29 import android.support.test.runner.AndroidJUnit4;
30 import android.util.Log;
31 
32 import org.junit.Assert;
33 import org.junit.Before;
34 import org.junit.Test;
35 import org.junit.runner.RunWith;
36 import org.mockito.ArgumentCaptor;
37 import org.mockito.Captor;
38 import org.mockito.Mock;
39 import org.mockito.MockitoAnnotations;
40 
41 import java.util.ArrayList;
42 import java.util.Arrays;
43 import java.util.Collections;
44 import java.util.List;
45 
46 @SmallTest
47 @RunWith(AndroidJUnit4.class)
48 public class MediaPlayerWrapperTest {
49     private static final int MSG_TIMEOUT = 0;
50 
51     private HandlerThread mThread;
52     private MediaMetadata.Builder mTestMetadata;
53     private ArrayList<MediaDescription.Builder> mTestQueue;
54     private PlaybackState.Builder mTestState;
55 
56     @Captor ArgumentCaptor<MediaController.Callback> mControllerCbs;
57     @Captor ArgumentCaptor<MediaData> mMediaUpdateData;
58     @Mock Log.TerribleFailureHandler mFailHandler;
59     @Mock MediaController mMockController;
60     @Mock MediaPlayerWrapper.Callback mTestCbs;
61 
getQueueFromDescriptions( List<MediaDescription.Builder> descriptions)62     List<MediaSession.QueueItem> getQueueFromDescriptions(
63             List<MediaDescription.Builder> descriptions) {
64         ArrayList<MediaSession.QueueItem> newList = new ArrayList<MediaSession.QueueItem>();
65 
66         for (MediaDescription.Builder bob : descriptions) {
67             newList.add(
68                     new MediaSession.QueueItem(
69                             bob.build(), Long.valueOf(bob.build().getMediaId())));
70         }
71 
72         return newList;
73     }
74 
75     @Before
setUp()76     public void setUp() {
77         MockitoAnnotations.initMocks(this);
78 
79         // Set failure handler to capture Log.wtf messages
80         Log.setWtfHandler(mFailHandler);
81 
82         // Set up Looper thread for the timeout handler
83         mThread = new HandlerThread("MediaPlayerWrapperTestThread");
84         mThread.start();
85 
86         // Set up new metadata that can be used in each test
87         mTestMetadata =
88                 new MediaMetadata.Builder()
89                         .putString(MediaMetadata.METADATA_KEY_TITLE, "BT Test Song")
90                         .putString(MediaMetadata.METADATA_KEY_ARTIST, "BT Test Artist")
91                         .putString(MediaMetadata.METADATA_KEY_ALBUM, "BT Test Album")
92                         .putLong(MediaMetadata.METADATA_KEY_DURATION, 5000L);
93 
94         mTestState =
95                 new PlaybackState.Builder()
96                         .setActiveQueueItemId(100)
97                         .setState(PlaybackState.STATE_PAUSED, 0, 1.0f);
98 
99         mTestQueue = new ArrayList<MediaDescription.Builder>();
100         mTestQueue.add(
101                 new MediaDescription.Builder()
102                         .setTitle("BT Test Song")
103                         .setSubtitle("BT Test Artist")
104                         .setDescription("BT Test Album")
105                         .setMediaId("100"));
106         mTestQueue.add(
107                 new MediaDescription.Builder()
108                         .setTitle("BT Test Song 2")
109                         .setSubtitle("BT Test Artist 2")
110                         .setDescription("BT Test Album 2")
111                         .setMediaId("101"));
112         mTestQueue.add(
113                 new MediaDescription.Builder()
114                         .setTitle("BT Test Song 3")
115                         .setSubtitle("BT Test Artist 3")
116                         .setDescription("BT Test Album 3")
117                         .setMediaId("102"));
118 
119         when(mMockController.getPackageName()).thenReturn("mMockController");
120         // NOTE: We use doReturn below because using the normal stubbing method
121         // doesn't immediately update the stub with the new return value and this
122         // can cause the old stub to be used.
123 
124         // Stub default metadata for the Media Controller
125         doReturn(mTestMetadata.build()).when(mMockController).getMetadata();
126         doReturn(mTestState.build()).when(mMockController).getPlaybackState();
127         doReturn(getQueueFromDescriptions(mTestQueue)).when(mMockController).getQueue();
128 
129         // Enable testing flag which enables Log.wtf statements. Some tests test against improper
130         // behaviour and the TerribleFailureListener is a good way to ensure that the error occurred
131         MediaPlayerWrapper.sTesting = true;
132     }
133 
134     /*
135      * Test to make sure that the wrapper fails to be built if passed invalid
136      * data.
137      */
138     @Test
testNullControllerLooper()139     public void testNullControllerLooper() {
140         MediaPlayerWrapper wrapper = MediaPlayerWrapper.wrap(null, mThread.getLooper());
141         Assert.assertNull(wrapper);
142 
143         wrapper = MediaPlayerWrapper.wrap(mMockController, null);
144         Assert.assertNull(wrapper);
145     }
146 
147     /*
148      * Test to make sure that isReady() returns false if there is no playback state,
149      * there is no metadata, or if the metadata has no title.
150      */
151     @Test
testIsReady()152     public void testIsReady() {
153         MediaPlayerWrapper wrapper = MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
154         Assert.assertTrue(wrapper.isReady());
155 
156         // Test isReady() is false when the playback state is null
157         doReturn(null).when(mMockController).getPlaybackState();
158         Assert.assertFalse(wrapper.isReady());
159 
160         // Restore the old playback state
161         doReturn(mTestState.build()).when(mMockController).getPlaybackState();
162         Assert.assertTrue(wrapper.isReady());
163 
164         // Test isReady() is false when the metadata is null
165         doReturn(null).when(mMockController).getMetadata();
166         Assert.assertFalse(wrapper.isReady());
167 
168         // Restore the old metadata
169         doReturn(mTestMetadata.build()).when(mMockController).getMetadata();
170         Assert.assertTrue(wrapper.isReady());
171     }
172 
173     /*
174      * Test to make sure that if a new controller is registered with different metadata than the
175      * previous controller, the new metadata is pulled upon registration.
176      */
177     @Test
testControllerUpdate()178     public void testControllerUpdate() {
179         // Create the wrapper object and register the looper with the timeout handler
180         MediaPlayerWrapper wrapper = MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
181         Assert.assertTrue(wrapper.isReady());
182         wrapper.registerCallback(mTestCbs);
183 
184         // Create a new MediaController that has different metadata than the previous controller
185         MediaController mUpdatedController = mock(MediaController.class);
186         doReturn(mTestState.build()).when(mUpdatedController).getPlaybackState();
187         mTestMetadata.putString(MediaMetadata.METADATA_KEY_TITLE, "New Title");
188         doReturn(mTestMetadata.build()).when(mUpdatedController).getMetadata();
189         doReturn(null).when(mMockController).getQueue();
190 
191         // Update the wrappers controller to the new controller
192         wrapper.updateMediaController(mUpdatedController);
193 
194         // Send a metadata update with the same data that the controller had upon registering
195         verify(mUpdatedController).registerCallback(mControllerCbs.capture(), any());
196         MediaController.Callback controllerCallbacks = mControllerCbs.getValue();
197         controllerCallbacks.onMetadataChanged(mTestMetadata.build());
198 
199         // Verify that a callback was never called since no data was updated
200         verify(mTestCbs, never()).mediaUpdatedCallback(any());
201     }
202 
203     /*
204      * Test to make sure that a media player update gets sent whenever a Media metadata or playback
205      * state change occurs instead of waiting for all data to be synced if the player doesn't
206      * support queues.
207      */
208     @Test
testNoQueueMediaUpdates()209     public void testNoQueueMediaUpdates() {
210         // Create the wrapper object and register the looper with the timeout handler
211         TestLooperManager looperManager = new TestLooperManager(mThread.getLooper());
212         MediaPlayerWrapper wrapper =
213                 MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
214         wrapper.registerCallback(mTestCbs);
215 
216         // Return null when getting the queue
217         doReturn(null).when(mMockController).getQueue();
218 
219         // Grab the callbacks the wrapper registered with the controller
220         verify(mMockController).registerCallback(mControllerCbs.capture(), any());
221         MediaController.Callback controllerCallbacks = mControllerCbs.getValue();
222 
223         // Update Metdata returned by controller
224         mTestMetadata.putString(MediaMetadata.METADATA_KEY_TITLE, "New Title");
225         doReturn(mTestMetadata.build()).when(mMockController).getMetadata();
226         controllerCallbacks.onMetadataChanged(mTestMetadata.build());
227 
228         // Assert that the metadata was updated and playback state wasn't
229         verify(mTestCbs, times(1)).mediaUpdatedCallback(mMediaUpdateData.capture());
230         MediaData data = mMediaUpdateData.getValue();
231         Assert.assertEquals(
232                 "Returned Metadata isn't equal to given Metadata",
233                 data.metadata,
234                 Util.toMetadata(mTestMetadata.build()));
235         Assert.assertEquals(
236                 "Returned PlaybackState isn't equal to original PlaybackState",
237                 data.state.toString(),
238                 mTestState.build().toString());
239         Assert.assertEquals("Returned Queue isn't empty", data.queue.size(), 0);
240 
241         // Update PlaybackState returned by controller
242         mTestState.setActiveQueueItemId(103);
243         doReturn(mTestState.build()).when(mMockController).getPlaybackState();
244         controllerCallbacks.onPlaybackStateChanged(mTestState.build());
245 
246         // Assert that the PlaybackState was changed but metadata stayed the same
247         verify(mTestCbs, times(2)).mediaUpdatedCallback(mMediaUpdateData.capture());
248         data = mMediaUpdateData.getValue();
249         Assert.assertEquals(
250                 "Returned PlaybackState isn't equal to given PlaybackState",
251                 data.state.toString(),
252                 mTestState.build().toString());
253         Assert.assertEquals(
254                 "Returned Metadata isn't equal to given Metadata",
255                 data.metadata,
256                 Util.toMetadata(mTestMetadata.build()));
257         Assert.assertEquals("Returned Queue isn't empty", data.queue.size(), 0);
258 
259         // Verify that there are no timeout messages pending and there were no timeouts
260         Assert.assertFalse(wrapper.getTimeoutHandler().hasMessages(MSG_TIMEOUT));
261         verify(mFailHandler, never()).onTerribleFailure(any(), any(), anyBoolean());
262     }
263 
264     /*
265      * This test updates the metadata and playback state returned by the
266      * controller then sends an update. This is to make sure that all relevant
267      * information is sent with every update. In the case without a queue,
268      * metadata and playback state are updated.
269      */
270     @Test
testDataOnUpdateNoQueue()271     public void testDataOnUpdateNoQueue() {
272         // Create the wrapper object and register the looper with the timeout handler
273         TestLooperManager looperManager = new TestLooperManager(mThread.getLooper());
274         MediaPlayerWrapper wrapper =
275                 MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
276         wrapper.registerCallback(mTestCbs);
277 
278         // Return null when getting the queue
279         doReturn(null).when(mMockController).getQueue();
280 
281         // Grab the callbacks the wrapper registered with the controller
282         verify(mMockController).registerCallback(mControllerCbs.capture(), any());
283         MediaController.Callback controllerCallbacks = mControllerCbs.getValue();
284 
285         // Update Metadata returned by controller
286         mTestMetadata.putString(MediaMetadata.METADATA_KEY_TITLE, "New Title");
287         doReturn(mTestMetadata.build()).when(mMockController).getMetadata();
288 
289         // Update PlaybackState returned by controller
290         mTestState.setActiveQueueItemId(103);
291         doReturn(mTestState.build()).when(mMockController).getPlaybackState();
292 
293         // Call the callback
294         controllerCallbacks.onPlaybackStateChanged(mTestState.build());
295 
296         // Assert that both metadata and playback state are there.
297         verify(mTestCbs, times(1)).mediaUpdatedCallback(mMediaUpdateData.capture());
298         MediaData data = mMediaUpdateData.getValue();
299         Assert.assertEquals(
300                 "Returned PlaybackState isn't equal to given PlaybackState",
301                 data.state.toString(),
302                 mTestState.build().toString());
303         Assert.assertEquals(
304                 "Returned Metadata isn't equal to given Metadata",
305                 data.metadata,
306                 Util.toMetadata(mTestMetadata.build()));
307         Assert.assertEquals("Returned Queue isn't empty", data.queue.size(), 0);
308 
309         // Verify that there are no timeout messages pending and there were no timeouts
310         Assert.assertFalse(wrapper.getTimeoutHandler().hasMessages(MSG_TIMEOUT));
311         verify(mFailHandler, never()).onTerribleFailure(any(), any(), anyBoolean());
312     }
313 
314     /*
315      * This test checks whether getCurrentMetadata() returns the corresponding item from
316      * the now playing list instead of the current metadata if there is a match.
317      */
318     @Test
testCurrentSongFromQueue()319     public void testCurrentSongFromQueue() {
320         // Create the wrapper object and register the looper with the timeout handler
321         TestLooperManager looperManager = new TestLooperManager(mThread.getLooper());
322 
323         mTestState.setActiveQueueItemId(101);
324         doReturn(mTestState.build()).when(mMockController).getPlaybackState();
325 
326         MediaPlayerWrapper wrapper =
327                 MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
328         wrapper.registerCallback(mTestCbs);
329 
330         // The current metadata doesn't contain track number info so check that
331         // field to see if the correct data was used.
332         Assert.assertEquals(wrapper.getCurrentMetadata().trackNum, "2");
333         Assert.assertEquals(wrapper.getCurrentMetadata().numTracks, "3");
334     }
335 
336     @Test
testNullMetadata()337     public void testNullMetadata() {
338         // Create the wrapper object and register the looper with the timeout handler
339         TestLooperManager looperManager = new TestLooperManager(mThread.getLooper());
340         MediaPlayerWrapper wrapper =
341                 MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
342         wrapper.registerCallback(mTestCbs);
343 
344         // Return null when getting the queue
345         doReturn(null).when(mMockController).getQueue();
346 
347         // Grab the callbacks the wrapper registered with the controller
348         verify(mMockController).registerCallback(mControllerCbs.capture(), any());
349         MediaController.Callback controllerCallbacks = mControllerCbs.getValue();
350 
351         // Update Metadata returned by controller
352         mTestMetadata.putString(MediaMetadata.METADATA_KEY_TITLE, "New Title");
353         doReturn(mTestMetadata.build()).when(mMockController).getMetadata();
354 
355         // Call the callback
356         controllerCallbacks.onMetadataChanged(null);
357 
358         // Assert that the metadata returned by getMetadata() is used instead of null
359         verify(mTestCbs, times(1)).mediaUpdatedCallback(mMediaUpdateData.capture());
360         MediaData data = mMediaUpdateData.getValue();
361         Assert.assertEquals("Returned metadata is incorrect", data.metadata,
362                 Util.toMetadata(mTestMetadata.build()));
363     }
364 
365     @Test
testNullQueue()366     public void testNullQueue() {
367         // Create the wrapper object and register the looper with the timeout handler
368         TestLooperManager looperManager = new TestLooperManager(mThread.getLooper());
369         MediaPlayerWrapper wrapper =
370                 MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
371         wrapper.registerCallback(mTestCbs);
372 
373         // Return null when getting the queue
374         doReturn(null).when(mMockController).getQueue();
375 
376         // Grab the callbacks the wrapper registered with the controller
377         verify(mMockController).registerCallback(mControllerCbs.capture(), any());
378         MediaController.Callback controllerCallbacks = mControllerCbs.getValue();
379 
380         // Call the callback
381         controllerCallbacks.onQueueChanged(null);
382 
383         // Assert that both metadata and playback state are there.
384         verify(mTestCbs, times(1)).mediaUpdatedCallback(mMediaUpdateData.capture());
385         MediaData data = mMediaUpdateData.getValue();
386         Assert.assertEquals("Returned Queue isn't null", data.queue.size(), 0);
387     }
388 
389     /*
390      * This test checks to see if the now playing queue data is cached.
391      */
392     @Test
testQueueCached()393     public void testQueueCached() {
394         // Create the wrapper object and register the looper with the timeout handler
395         TestLooperManager looperManager = new TestLooperManager(mThread.getLooper());
396         MediaPlayerWrapper wrapper =
397                 MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
398         wrapper.registerCallback(mTestCbs);
399 
400         // Call getCurrentQueue() multiple times.
401         for (int i = 0; i < 3; i++) {
402             Assert.assertEquals(wrapper.getCurrentQueue(),
403                     Util.toMetadataList(getQueueFromDescriptions(mTestQueue)));
404         }
405 
406         // Verify that getQueue() was only called twice. Once on creation and once during
407         // registration
408         verify(mMockController, times(2)).getQueue();
409     }
410 
411     /*
412      * This test sends repeated Playback State updates that only have a short
413      * position update change to see if they get debounced.
414      */
415     @Test
testPlaybackStateUpdateSpam()416     public void testPlaybackStateUpdateSpam() {
417         // Create the wrapper object and register the looper with the timeout handler
418         TestLooperManager looperManager = new TestLooperManager(mThread.getLooper());
419         MediaPlayerWrapper wrapper =
420                 MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
421         wrapper.registerCallback(mTestCbs);
422 
423         // Return null when getting the queue
424         doReturn(null).when(mMockController).getQueue();
425 
426         // Grab the callbacks the wrapper registered with the controller
427         verify(mMockController).registerCallback(mControllerCbs.capture(), any());
428         MediaController.Callback controllerCallbacks = mControllerCbs.getValue();
429 
430         // Update PlaybackState returned by controller (Should trigger update)
431         mTestState.setState(PlaybackState.STATE_PLAYING, 1000, 1.0f);
432         doReturn(mTestState.build()).when(mMockController).getPlaybackState();
433         controllerCallbacks.onPlaybackStateChanged(mTestState.build());
434 
435         // Assert that both metadata and only the first playback state is there.
436         verify(mTestCbs, times(1)).mediaUpdatedCallback(mMediaUpdateData.capture());
437         MediaData data = mMediaUpdateData.getValue();
438         Assert.assertEquals(
439                 "Returned PlaybackState isn't equal to given PlaybackState",
440                 data.state.toString(),
441                 mTestState.build().toString());
442         Assert.assertEquals(
443                 "Returned Metadata isn't equal to given Metadata",
444                 data.metadata,
445                 Util.toMetadata(mTestMetadata.build()));
446         Assert.assertEquals("Returned Queue isn't empty", data.queue.size(), 0);
447 
448         // Update PlaybackState returned by controller (Shouldn't trigger update)
449         mTestState.setState(PlaybackState.STATE_PLAYING, 1020, 1.0f);
450         doReturn(mTestState.build()).when(mMockController).getPlaybackState();
451         controllerCallbacks.onPlaybackStateChanged(mTestState.build());
452 
453         // Update PlaybackState returned by controller (Shouldn't trigger update)
454         mTestState.setState(PlaybackState.STATE_PLAYING, 1040, 1.0f);
455         doReturn(mTestState.build()).when(mMockController).getPlaybackState();
456         controllerCallbacks.onPlaybackStateChanged(mTestState.build());
457 
458         // Update PlaybackState returned by controller (Should trigger update)
459         mTestState.setState(PlaybackState.STATE_PLAYING, 3000, 1.0f);
460         doReturn(mTestState.build()).when(mMockController).getPlaybackState();
461         controllerCallbacks.onPlaybackStateChanged(mTestState.build());
462 
463 
464         // Verify that there are no timeout messages pending and there were no timeouts
465         Assert.assertFalse(wrapper.getTimeoutHandler().hasMessages(MSG_TIMEOUT));
466         verify(mFailHandler, never()).onTerribleFailure(any(), any(), anyBoolean());
467     }
468 
469     /*
470      * Check to make sure that cleanup tears down the object properly
471      */
472     @Test
testWrapperCleanup()473     public void testWrapperCleanup() {
474         // Create the wrapper object and register the looper with the timeout handler
475         TestLooperManager looperManager = new TestLooperManager(mThread.getLooper());
476         MediaPlayerWrapper wrapper =
477                 MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
478         wrapper.registerCallback(mTestCbs);
479 
480         // Cleanup the wrapper
481         wrapper.cleanup();
482 
483         // Ensure that everything was cleaned up
484         verify(mMockController).unregisterCallback(any());
485         Assert.assertNull(wrapper.getTimeoutHandler());
486     }
487 
488     /*
489      * Test to check that a PlaybackState of none is being ignored as that usually means that the
490      * MediaController isn't ready.
491      */
492     @Test
testIgnorePlaystateNone()493     public void testIgnorePlaystateNone() {
494         // Create the wrapper object and register the looper with the timeout handler
495         TestLooperManager looperManager = new TestLooperManager(mThread.getLooper());
496         MediaPlayerWrapper wrapper =
497                 MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
498         wrapper.registerCallback(mTestCbs);
499 
500         // Grab the callbacks the wrapper registered with the controller
501         verify(mMockController).registerCallback(mControllerCbs.capture(), any());
502         MediaController.Callback controllerCallbacks = mControllerCbs.getValue();
503 
504         // Update PlaybackState returned by controller
505         mTestState.setState(PlaybackState.STATE_NONE, 0, 1.0f);
506         doReturn(mTestState.build()).when(mMockController).getPlaybackState();
507         controllerCallbacks.onPlaybackStateChanged(mTestState.build());
508 
509         // Verify that there was no update
510         verify(mTestCbs, never()).mediaUpdatedCallback(any());
511 
512         // Verify that there are no timeout messages pending and there were no timeouts
513         Assert.assertFalse(wrapper.getTimeoutHandler().hasMessages(MSG_TIMEOUT));
514         verify(mFailHandler, never()).onTerribleFailure(any(), any(), anyBoolean());
515     }
516 
517     /*
518      * Test to make sure that on a controller that supports browsing, the
519      * Media Metadata, Queue, and Playback state all have to be in sync in
520      * order for a media update to be sent via registered callback.
521      */
522     @Test
testMetadataSync()523     public void testMetadataSync() {
524         // Create the wrapper object and register the looper with the timeout handler
525         TestLooperManager looperManager = new TestLooperManager(mThread.getLooper());
526         MediaPlayerWrapper wrapper =
527                 MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
528         wrapper.registerCallback(mTestCbs);
529 
530         // Grab the callbacks the wrapper registered with the controller
531         verify(mMockController).registerCallback(mControllerCbs.capture(), any());
532         MediaController.Callback controllerCallbacks = mControllerCbs.getValue();
533 
534         // Update Metadata returned by controller
535         mTestMetadata.putString(MediaMetadata.METADATA_KEY_TITLE, "New Title");
536         doReturn(mTestMetadata.build()).when(mMockController).getMetadata();
537         controllerCallbacks.onMetadataChanged(mTestMetadata.build());
538 
539         // Update PlaybackState returned by controller
540         mTestState.setActiveQueueItemId(103);
541         doReturn(mTestState.build()).when(mMockController).getPlaybackState();
542         controllerCallbacks.onPlaybackStateChanged(mTestState.build());
543 
544         // Update Queue returned by controller
545         mTestQueue.add(
546                 new MediaDescription.Builder()
547                         .setTitle("New Title")
548                         .setSubtitle("BT Test Artist")
549                         .setDescription("BT Test Album")
550                         .setMediaId("103"));
551         doReturn(getQueueFromDescriptions(mTestQueue)).when(mMockController).getQueue();
552         controllerCallbacks.onQueueChanged(getQueueFromDescriptions(mTestQueue));
553 
554         // Assert that the callback was called with the updated data
555         verify(mTestCbs, times(1)).mediaUpdatedCallback(mMediaUpdateData.capture());
556         verify(mFailHandler, never()).onTerribleFailure(any(), any(), anyBoolean());
557         MediaData data = mMediaUpdateData.getValue();
558         Assert.assertEquals(
559                 "Returned Metadata isn't equal to given Metadata",
560                 data.metadata,
561                 Util.toMetadata(mTestMetadata.build()));
562         Assert.assertEquals(
563                 "Returned PlaybackState isn't equal to given PlaybackState",
564                 data.state.toString(),
565                 mTestState.build().toString());
566         Assert.assertEquals(
567                 "Returned Queue isn't equal to given Queue",
568                 data.queue,
569                 Util.toMetadataList(getQueueFromDescriptions(mTestQueue)));
570 
571         // Verify that there are no timeout messages pending and there were no timeouts
572         Assert.assertFalse(wrapper.getTimeoutHandler().hasMessages(MSG_TIMEOUT));
573         verify(mFailHandler, never()).onTerribleFailure(any(), any(), anyBoolean());
574     }
575 
576     /*
577      * Test to make sure that an error occurs when the MediaController fails to
578      * update all its media data in a resonable amount of time.
579      */
580     @Test
testMetadataSyncFail()581     public void testMetadataSyncFail() {
582         // Create the wrapper object and register the looper with the timeout handler
583         TestLooperManager looperManager =
584                 InstrumentationRegistry.getInstrumentation()
585                         .acquireLooperManager(mThread.getLooper());
586         MediaPlayerWrapper wrapper =
587                 MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
588         wrapper.registerCallback(mTestCbs);
589 
590         // Grab the callbacks the wrapper registered with the controller
591         verify(mMockController).registerCallback(mControllerCbs.capture(), any());
592         MediaController.Callback controllerCallbacks = mControllerCbs.getValue();
593 
594         // Update Metadata returned by controller
595         mTestMetadata.putString(MediaMetadata.METADATA_KEY_TITLE, "Mismatch Title");
596         doReturn(mTestMetadata.build()).when(mMockController).getMetadata();
597         controllerCallbacks.onMetadataChanged(mTestMetadata.build());
598 
599         // Force the timeout to execute immediately
600         looperManager.execute(looperManager.next());
601 
602         // Assert that there was a timeout
603         verify(mFailHandler).onTerribleFailure(any(), any(), anyBoolean());
604 
605         // Assert that the callback was called with the mismatch data
606         verify(mTestCbs, times(1)).mediaUpdatedCallback(mMediaUpdateData.capture());
607         MediaData data = mMediaUpdateData.getValue();
608         Assert.assertEquals(
609                 "Returned Metadata isn't equal to given Metadata",
610                 data.metadata,
611                 Util.toMetadata(mTestMetadata.build()));
612         Assert.assertEquals(
613                 "Returned PlaybackState isn't equal to given PlaybackState",
614                 data.state.toString(),
615                 mTestState.build().toString());
616         Assert.assertEquals(
617                 "Returned Queue isn't equal to given Queue",
618                 data.queue,
619                 Util.toMetadataList(getQueueFromDescriptions(mTestQueue)));
620     }
621 
622     /*
623      * testMetadataSyncFuzz() tests for the same conditions as testMetadataSync()
624      * but randomizes the order in which the MediaController update callbacks are
625      * called. The test is repeated 100 times for completeness.
626      */
627     @Test
testMetadataSyncFuzz()628     public void testMetadataSyncFuzz() {
629         // The number of times the random order test is run
630         final int numTestLoops = 100;
631 
632         // Create the wrapper object and register the looper with the timeout handler
633         TestLooperManager looperManager =
634                 InstrumentationRegistry.getInstrumentation()
635                         .acquireLooperManager(mThread.getLooper());
636         MediaPlayerWrapper wrapper =
637                 MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
638         wrapper.registerCallback(mTestCbs);
639 
640         // Grab the callbacks the wrapper registered with the controller
641         verify(mMockController).registerCallback(mControllerCbs.capture(), any());
642         MediaController.Callback controllerCallbacks = mControllerCbs.getValue();
643 
644         MediaMetadata.Builder m = new MediaMetadata.Builder();
645         PlaybackState.Builder s = new PlaybackState.Builder();
646         s.setState(PlaybackState.STATE_PAUSED, 0, 1.0f);
647         MediaDescription.Builder d = new MediaDescription.Builder();
648         for (int i = 1; i <= numTestLoops; i++) {
649             // Setup Media Info for current itteration
650             m.putString(MediaMetadata.METADATA_KEY_TITLE, "BT Fuzz Song " + i);
651             m.putString(MediaMetadata.METADATA_KEY_ARTIST, "BT Fuzz Artist " + i);
652             m.putString(MediaMetadata.METADATA_KEY_ALBUM, "BT Fuzz Album " + i);
653             m.putLong(MediaMetadata.METADATA_KEY_DURATION, 5000L);
654             s.setActiveQueueItemId(i);
655             d.setTitle("BT Fuzz Song " + i);
656             d.setSubtitle("BT Fuzz Artist " + i);
657             d.setDescription("BT Fuzz Album " + i);
658             d.setMediaId(Integer.toString(i));
659 
660             // Create a new Queue each time to prevent double counting caused by
661             // Playback State matching the updated Queue
662             ArrayList<MediaSession.QueueItem> q = new ArrayList<MediaSession.QueueItem>();
663             q.add(new MediaSession.QueueItem(d.build(), i));
664 
665             // Call the MediaController callbacks in a random order
666             ArrayList<Integer> callbackOrder = new ArrayList<>(Arrays.asList(0, 1, 2));
667             Collections.shuffle(callbackOrder);
668             for (int j = 0; j < 3; j++) {
669                 switch (callbackOrder.get(j)) {
670                     case 0: // Update Metadata
671                         doReturn(m.build()).when(mMockController).getMetadata();
672                         controllerCallbacks.onMetadataChanged(m.build());
673                         break;
674                     case 1: // Update PlaybackState
675                         doReturn(s.build()).when(mMockController).getPlaybackState();
676                         controllerCallbacks.onPlaybackStateChanged(s.build());
677                         break;
678                     case 2: // Update Queue
679                         doReturn(q).when(mMockController).getQueue();
680                         controllerCallbacks.onQueueChanged(q);
681                         break;
682                 }
683             }
684 
685             // Check that the callback was called a certain number of times and
686             // that all the Media info matches what was given
687             verify(mTestCbs, times(i)).mediaUpdatedCallback(mMediaUpdateData.capture());
688             MediaData data = mMediaUpdateData.getValue();
689             Assert.assertEquals(
690                     "Returned Metadata isn't equal to given Metadata",
691                     data.metadata,
692                     Util.toMetadata(m.build()));
693             Assert.assertEquals(
694                     "Returned PlaybackState isn't equal to given PlaybackState",
695                     data.state.toString(),
696                     s.build().toString());
697             Assert.assertEquals("Returned Queue isn't equal to given Queue",
698                     data.queue,
699                     Util.toMetadataList(q));
700         }
701 
702         // Verify that there are no timeout messages pending and there were no timeouts
703         Assert.assertFalse(wrapper.getTimeoutHandler().hasMessages(MSG_TIMEOUT));
704         verify(mFailHandler, never()).onTerribleFailure(any(), any(), anyBoolean());
705     }
706 }
707