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
17package com.android.bluetooth.avrcp;
18
19import static org.mockito.Mockito.*;
20
21import android.media.MediaDescription;
22import android.media.MediaMetadata;
23import android.media.session.MediaSession;
24import android.media.session.PlaybackState;
25import android.os.HandlerThread;
26import android.os.TestLooperManager;
27import android.support.test.InstrumentationRegistry;
28import android.support.test.filters.SmallTest;
29import android.support.test.runner.AndroidJUnit4;
30import android.util.Log;
31
32import org.junit.Assert;
33import org.junit.Before;
34import org.junit.Test;
35import org.junit.runner.RunWith;
36import org.mockito.ArgumentCaptor;
37import org.mockito.Captor;
38import org.mockito.Mock;
39import org.mockito.MockitoAnnotations;
40
41import java.util.ArrayList;
42import java.util.Arrays;
43import java.util.Collections;
44import java.util.List;
45
46@SmallTest
47@RunWith(AndroidJUnit4.class)
48public 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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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