• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2019 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 package com.android.bluetooth.avrcpcontroller;
17 
18 import static org.mockito.Mockito.*;
19 
20 import android.bluetooth.BluetoothAdapter;
21 import android.bluetooth.BluetoothAvrcpController;
22 import android.bluetooth.BluetoothDevice;
23 import android.bluetooth.BluetoothProfile;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.res.Resources;
27 import android.media.AudioManager;
28 import android.os.Looper;
29 import android.support.v4.media.session.MediaControllerCompat;
30 import android.support.v4.media.session.PlaybackStateCompat;
31 
32 import androidx.test.InstrumentationRegistry;
33 import androidx.test.filters.FlakyTest;
34 import androidx.test.filters.MediumTest;
35 import androidx.test.rule.ServiceTestRule;
36 import androidx.test.runner.AndroidJUnit4;
37 
38 import com.android.bluetooth.R;
39 import com.android.bluetooth.TestUtils;
40 import com.android.bluetooth.a2dpsink.A2dpSinkService;
41 import com.android.bluetooth.btservice.AdapterService;
42 import com.android.bluetooth.btservice.ProfileService;
43 
44 import org.hamcrest.core.IsInstanceOf;
45 import org.junit.After;
46 import org.junit.Assert;
47 import org.junit.Assume;
48 import org.junit.Before;
49 import org.junit.Rule;
50 import org.junit.Test;
51 import org.junit.runner.RunWith;
52 import org.mockito.ArgumentCaptor;
53 import org.mockito.Mock;
54 import org.mockito.MockitoAnnotations;
55 
56 import java.util.ArrayList;
57 import java.util.List;
58 import java.util.UUID;
59 
60 @MediumTest
61 @RunWith(AndroidJUnit4.class)
62 public class AvrcpControllerStateMachineTest {
63     private static final int ASYNC_CALL_TIMEOUT_MILLIS = 100;
64     private static final int CONNECT_TIMEOUT_TEST_MILLIS = 1000;
65     private static final int KEY_DOWN = 0;
66     private static final int KEY_UP = 1;
67     private AvrcpControllerStateMachine mAvrcpStateMachine;
68     private BluetoothAdapter mAdapter;
69     private Context mTargetContext;
70     private BluetoothDevice mTestDevice;
71     private ArgumentCaptor<Intent> mIntentArgument = ArgumentCaptor.forClass(Intent.class);
72     private byte[] mTestAddress = new byte[]{00, 01, 02, 03, 04, 05};
73 
74     @Rule public final ServiceTestRule mAvrcpServiceRule = new ServiceTestRule();
75     @Rule public final ServiceTestRule mA2dpServiceRule = new ServiceTestRule();
76 
77     @Mock
78     private AdapterService mAvrcpAdapterService;
79 
80     @Mock
81     private AdapterService mA2dpAdapterService;
82 
83     @Mock
84     private AudioManager mAudioManager;
85     @Mock
86     private AvrcpControllerService mAvrcpControllerService;
87     @Mock
88     private A2dpSinkService mA2dpSinkService;
89 
90     @Mock
91     private Resources mMockResources;
92 
93 
94     @Before
setUp()95     public void setUp() throws Exception {
96         mTargetContext = InstrumentationRegistry.getTargetContext();
97         Assume.assumeTrue("Ignore test when AVRCP Controller is not enabled",
98                 mTargetContext.getResources().getBoolean(
99                         R.bool.profile_supported_avrcp_controller));
100         if (Looper.myLooper() == null) {
101             Looper.prepare();
102         }
103         Assert.assertNotNull(Looper.myLooper());
104 
105         // Setup mocks and test assets
106         MockitoAnnotations.initMocks(this);
107         TestUtils.setAdapterService(mAvrcpAdapterService);
108         TestUtils.startService(mAvrcpServiceRule, AvrcpControllerService.class);
109         TestUtils.clearAdapterService(mAvrcpAdapterService);
110         TestUtils.setAdapterService(mA2dpAdapterService);
111         TestUtils.startService(mA2dpServiceRule, A2dpSinkService.class);
112         when(mA2dpSinkService.setActiveDeviceNative(any())).thenReturn(true);
113 
114         when(mMockResources.getBoolean(R.bool.a2dp_sink_automatically_request_audio_focus))
115                 .thenReturn(true);
116         doReturn(mMockResources).when(mAvrcpControllerService).getResources();
117         A2dpSinkService.setA2dpSinkService(mA2dpSinkService);
118         doReturn(15).when(mAudioManager).getStreamMaxVolume(anyInt());
119         doReturn(8).when(mAudioManager).getStreamVolume(anyInt());
120         doReturn(true).when(mAudioManager).isVolumeFixed();
121         doReturn(mAudioManager).when(mAvrcpControllerService)
122                 .getSystemService(Context.AUDIO_SERVICE);
123 
124         // This line must be called to make sure relevant objects are initialized properly
125         mAdapter = BluetoothAdapter.getDefaultAdapter();
126         // Get a device for testing
127         mTestDevice = mAdapter.getRemoteDevice(mTestAddress);
128         mAvrcpControllerService.start();
129         mAvrcpControllerService.sBrowseTree = new BrowseTree(null);
130         mAvrcpStateMachine = new AvrcpControllerStateMachine(mTestDevice, mAvrcpControllerService);
131         mAvrcpStateMachine.start();
132     }
133 
134     @After
tearDown()135     public void tearDown() throws Exception {
136         if (!mTargetContext.getResources().getBoolean(R.bool.profile_supported_avrcp_controller)) {
137             return;
138         }
139 
140         mAvrcpStateMachine.disconnect();
141         TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper());
142         Assert.assertFalse(mAvrcpStateMachine.isActive());
143 
144         TestUtils.clearAdapterService(mA2dpAdapterService);
145     }
146 
147     /**
148      * Test to confirm that the state machine is capable of cycling through the 4
149      * connection states, and that upon completion, it cleans up aftwards.
150      */
151     @Test
testDisconnect()152     public void testDisconnect() {
153         int numBroadcastsSent = setUpConnectedState(true, true);
154         StackEvent event =
155                 StackEvent.connectionStateChanged(false, false);
156 
157         mAvrcpStateMachine.disconnect();
158         numBroadcastsSent += 2;
159         verify(mAvrcpControllerService,
160                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcast(
161                 mIntentArgument.capture(), eq(ProfileService.BLUETOOTH_PERM));
162         Assert.assertEquals(mTestDevice, mIntentArgument.getValue().getParcelableExtra(
163                 BluetoothDevice.EXTRA_DEVICE));
164         Assert.assertEquals(BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED,
165                 mIntentArgument.getValue().getAction());
166         Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
167                 mIntentArgument.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
168         Assert.assertThat(mAvrcpStateMachine.getCurrentState(),
169                 IsInstanceOf.instanceOf(AvrcpControllerStateMachine.Disconnected.class));
170         Assert.assertEquals(mAvrcpStateMachine.getState(), BluetoothProfile.STATE_DISCONNECTED);
171         verify(mAvrcpControllerService).removeStateMachine(eq(mAvrcpStateMachine));
172         MediaControllerCompat.TransportControls transportControls =
173                 BluetoothMediaBrowserService.getTransportControls();
174         Assert.assertEquals(PlaybackStateCompat.STATE_ERROR,
175                 BluetoothMediaBrowserService.getPlaybackState());
176     }
177 
178     /**
179      * Test to confirm that a control only device can be established (no browsing)
180      */
181     @Test
testControlOnly()182     public void testControlOnly() {
183         int numBroadcastsSent = setUpConnectedState(true, false);
184         MediaControllerCompat.TransportControls transportControls =
185                 BluetoothMediaBrowserService.getTransportControls();
186         Assert.assertNotNull(transportControls);
187         Assert.assertEquals(PlaybackStateCompat.STATE_NONE,
188                 BluetoothMediaBrowserService.getPlaybackState());
189         StackEvent event =
190                 StackEvent.connectionStateChanged(false, false);
191         mAvrcpStateMachine.disconnect();
192         numBroadcastsSent += 2;
193         verify(mAvrcpControllerService,
194                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcast(
195                 mIntentArgument.capture(), eq(ProfileService.BLUETOOTH_PERM));
196         Assert.assertEquals(mTestDevice, mIntentArgument.getValue().getParcelableExtra(
197                 BluetoothDevice.EXTRA_DEVICE));
198         Assert.assertEquals(BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED,
199                 mIntentArgument.getValue().getAction());
200         Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
201                 mIntentArgument.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
202         Assert.assertThat(mAvrcpStateMachine.getCurrentState(),
203                 IsInstanceOf.instanceOf(AvrcpControllerStateMachine.Disconnected.class));
204         Assert.assertEquals(mAvrcpStateMachine.getState(), BluetoothProfile.STATE_DISCONNECTED);
205         verify(mAvrcpControllerService).removeStateMachine(eq(mAvrcpStateMachine));
206         Assert.assertEquals(PlaybackStateCompat.STATE_ERROR,
207                 BluetoothMediaBrowserService.getPlaybackState());
208     }
209 
210     /**
211      * Test to confirm that a browsing only device can be established (no control)
212      */
213     @Test
214     @FlakyTest
testBrowsingOnly()215     public void testBrowsingOnly() {
216         Assert.assertEquals(0, mAvrcpControllerService.sBrowseTree.mRootNode.getChildrenCount());
217         int numBroadcastsSent = setUpConnectedState(false, true);
218         Assert.assertEquals(1, mAvrcpControllerService.sBrowseTree.mRootNode.getChildrenCount());
219         Assert.assertEquals(PlaybackStateCompat.STATE_NONE,
220                 BluetoothMediaBrowserService.getPlaybackState());
221         StackEvent event =
222                 StackEvent.connectionStateChanged(false, false);
223         mAvrcpStateMachine.disconnect();
224         numBroadcastsSent += 2;
225         verify(mAvrcpControllerService,
226                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcast(
227                 mIntentArgument.capture(), eq(ProfileService.BLUETOOTH_PERM));
228         Assert.assertEquals(mTestDevice, mIntentArgument.getValue().getParcelableExtra(
229                 BluetoothDevice.EXTRA_DEVICE));
230         Assert.assertEquals(BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED,
231                 mIntentArgument.getValue().getAction());
232         Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
233                 mIntentArgument.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
234         Assert.assertThat(mAvrcpStateMachine.getCurrentState(),
235                 IsInstanceOf.instanceOf(AvrcpControllerStateMachine.Disconnected.class));
236         Assert.assertEquals(mAvrcpStateMachine.getState(), BluetoothProfile.STATE_DISCONNECTED);
237         verify(mAvrcpControllerService).removeStateMachine(eq(mAvrcpStateMachine));
238         MediaControllerCompat.TransportControls transportControls =
239                 BluetoothMediaBrowserService.getTransportControls();
240         Assert.assertEquals(PlaybackStateCompat.STATE_ERROR,
241                 BluetoothMediaBrowserService.getPlaybackState());
242     }
243 
244     /**
245      * Test to make sure the state machine is tracking the correct device
246      */
247     @Test
testGetDevice()248     public void testGetDevice() {
249         Assert.assertEquals(mAvrcpStateMachine.getDevice(), mTestDevice);
250     }
251 
252     /**
253      * Test that dumpsys will generate information about connected devices
254      */
255     @Test
testDump()256     public void testDump() {
257         StringBuilder sb = new StringBuilder();
258         mAvrcpStateMachine.dump(sb);
259         Assert.assertEquals(sb.toString(),
260                 "  mDevice: " + mTestDevice.toString()
261                 + "(null) name=AvrcpControllerStateMachine state=Disconnected\n"
262                 + "  isActive: false\n");
263     }
264 
265     /**
266      * Test media browser play command
267      */
268     @Test
testPlay()269     public void testPlay() throws Exception {
270         setUpConnectedState(true, true);
271         MediaControllerCompat.TransportControls transportControls =
272                 BluetoothMediaBrowserService.getTransportControls();
273 
274         //Play
275         transportControls.play();
276         verify(mAvrcpControllerService,
277                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
278                 eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_PLAY), eq(KEY_DOWN));
279         verify(mAvrcpControllerService,
280                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
281                 eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_PLAY), eq(KEY_UP));
282     }
283 
284     /**
285      * Test media browser pause command
286      */
287     @Test
testPause()288     public void testPause() throws Exception {
289         setUpConnectedState(true, true);
290         MediaControllerCompat.TransportControls transportControls =
291                 BluetoothMediaBrowserService.getTransportControls();
292 
293         //Pause
294         transportControls.pause();
295         verify(mAvrcpControllerService,
296                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
297                 eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE), eq(KEY_DOWN));
298         verify(mAvrcpControllerService,
299                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
300                 eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE), eq(KEY_UP));
301     }
302 
303     /**
304      * Test media browser stop command
305      */
306     @Test
testStop()307     public void testStop() throws Exception {
308         setUpConnectedState(true, true);
309         MediaControllerCompat.TransportControls transportControls =
310                 BluetoothMediaBrowserService.getTransportControls();
311 
312         //Stop
313         transportControls.stop();
314         verify(mAvrcpControllerService,
315                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
316                 eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_STOP), eq(KEY_DOWN));
317         verify(mAvrcpControllerService,
318                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
319                 eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_STOP), eq(KEY_UP));
320     }
321 
322     /**
323      * Test media browser next command
324      */
325     @Test
testNext()326     public void testNext() throws Exception {
327         setUpConnectedState(true, true);
328         MediaControllerCompat.TransportControls transportControls =
329                 BluetoothMediaBrowserService.getTransportControls();
330 
331         //Next
332         transportControls.skipToNext();
333         verify(mAvrcpControllerService,
334                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
335                 eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_FORWARD),
336                 eq(KEY_DOWN));
337         verify(mAvrcpControllerService,
338                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
339                 eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_FORWARD), eq(KEY_UP));
340     }
341 
342     /**
343      * Test media browser previous command
344      */
345     @Test
testPrevious()346     public void testPrevious() throws Exception {
347         setUpConnectedState(true, true);
348         MediaControllerCompat.TransportControls transportControls =
349                 BluetoothMediaBrowserService.getTransportControls();
350 
351         //Previous
352         transportControls.skipToPrevious();
353         verify(mAvrcpControllerService,
354                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
355                 eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_BACKWARD),
356                 eq(KEY_DOWN));
357         verify(mAvrcpControllerService,
358                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
359                 eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_BACKWARD), eq(KEY_UP));
360     }
361 
362     /**
363      * Test media browser fast forward command
364      */
365     @Test
366     @FlakyTest
testFastForward()367     public void testFastForward() throws Exception {
368         setUpConnectedState(true, true);
369         MediaControllerCompat.TransportControls transportControls =
370                 BluetoothMediaBrowserService.getTransportControls();
371 
372         //FastForward
373         transportControls.fastForward();
374         verify(mAvrcpControllerService,
375                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
376                 eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_FF), eq(KEY_DOWN));
377         //Finish FastForwarding
378         transportControls.play();
379         verify(mAvrcpControllerService,
380                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
381                 eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_FF), eq(KEY_UP));
382     }
383 
384     /**
385      * Test media browser rewind command
386      */
387     @Test
testRewind()388     public void testRewind() throws Exception {
389         setUpConnectedState(true, true);
390         MediaControllerCompat.TransportControls transportControls =
391                 BluetoothMediaBrowserService.getTransportControls();
392 
393         //Rewind
394         transportControls.rewind();
395         verify(mAvrcpControllerService,
396                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
397                 eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_REWIND), eq(KEY_DOWN));
398         //Finish Rewinding
399         transportControls.play();
400         verify(mAvrcpControllerService,
401                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
402                 eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_REWIND), eq(KEY_UP));
403     }
404 
405     /**
406      * Test media browser skip to queue item
407      */
408     @Test
testSkipToQueueInvalid()409     public void testSkipToQueueInvalid() throws Exception {
410         byte scope = 1;
411         int minSize = 0;
412         int maxSize = 255;
413         setUpConnectedState(true, true);
414         MediaControllerCompat.TransportControls transportControls =
415                 BluetoothMediaBrowserService.getTransportControls();
416 
417         //Play an invalid item below start
418         transportControls.skipToQueueItem(minSize - 1);
419         verify(mAvrcpControllerService,
420                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(0)).playItemNative(
421                 eq(mTestAddress), eq(scope), anyLong(), anyInt());
422 
423         //Play an invalid item beyond end
424         transportControls.skipToQueueItem(maxSize + 1);
425         verify(mAvrcpControllerService,
426                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(0)).playItemNative(
427                 eq(mTestAddress), eq(scope), anyLong(), anyInt());
428     }
429 
430     /**
431      * Test media browser shuffle command
432      */
433     @Test
testShuffle()434     public void testShuffle() throws Exception {
435         byte[] shuffleSetting = new byte[]{3};
436         byte[] shuffleMode = new byte[]{2};
437 
438         setUpConnectedState(true, true);
439         MediaControllerCompat.TransportControls transportControls =
440                 BluetoothMediaBrowserService.getTransportControls();
441 
442         //Shuffle
443         transportControls.setShuffleMode(1);
444         verify(mAvrcpControllerService, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1))
445                 .setPlayerApplicationSettingValuesNative(
446                 eq(mTestAddress), eq((byte) 1), eq(shuffleSetting), eq(shuffleMode));
447     }
448 
449     /**
450      * Test media browser repeat command
451      */
452     @Test
testRepeat()453     public void testRepeat() throws Exception {
454         byte[] repeatSetting = new byte[]{2};
455         byte[] repeatMode = new byte[]{3};
456 
457         setUpConnectedState(true, true);
458         MediaControllerCompat.TransportControls transportControls =
459                 BluetoothMediaBrowserService.getTransportControls();
460 
461         //Shuffle
462         transportControls.setRepeatMode(2);
463         verify(mAvrcpControllerService, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1))
464                 .setPlayerApplicationSettingValuesNative(
465                 eq(mTestAddress), eq((byte) 1), eq(repeatSetting), eq(repeatMode));
466     }
467 
468     /**
469      * Test media browsing
470      * Verify that a browse tree is created with the proper root
471      * Verify that a player can be fetched and added to the browse tree
472      * Verify that the contents of a player are fetched upon request
473      */
474     @Test
475     @FlakyTest
testBrowsingCommands()476     public void testBrowsingCommands() {
477         setUpConnectedState(true, true);
478         final String rootName = "__ROOT__";
479         final String playerName = "Player 1";
480 
481         //Get the root of the device
482         BrowseTree.BrowseNode results = mAvrcpStateMachine.findNode(rootName);
483         Assert.assertEquals(rootName + mTestDevice.toString(), results.getID());
484 
485         //Request fetch the list of players
486         BrowseTree.BrowseNode playerNodes = mAvrcpStateMachine.findNode(results.getID());
487         mAvrcpStateMachine.requestContents(results);
488         verify(mAvrcpControllerService,
489                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).getPlayerListNative(eq(mTestAddress),
490                 eq(0), eq(19));
491 
492         //Provide back a player object
493         byte[] playerFeatures =
494                 new byte[]{0, 0, 0, 0, 0, (byte) 0xb7, 0x01, 0x0c, 0x0a, 0, 0, 0, 0, 0, 0, 0};
495         AvrcpPlayer playerOne = new AvrcpPlayer(mTestDevice, 1, playerName, playerFeatures, 1, 1);
496         List<AvrcpPlayer> testPlayers = new ArrayList<>();
497         testPlayers.add(playerOne);
498         mAvrcpStateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_PLAYER_ITEMS,
499                 testPlayers);
500 
501         //Verify that the player object is available.
502         mAvrcpStateMachine.requestContents(results);
503         verify(mAvrcpControllerService,
504                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).getPlayerListNative(eq(mTestAddress),
505                 eq(1), eq(0));
506         mAvrcpStateMachine.sendMessage(
507                 AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_FOLDER_ITEMS_OUT_OF_RANGE);
508         playerNodes = mAvrcpStateMachine.findNode(results.getID());
509         Assert.assertEquals(true, results.isCached());
510         Assert.assertEquals("MediaItem{mFlags=1, mDescription=" + playerName + ", null, null}",
511                 results.getChildren().get(0).getMediaItem().toString());
512 
513         //Fetch contents of that player object
514         BrowseTree.BrowseNode playerOneNode = mAvrcpStateMachine.findNode(
515                 results.getChildren().get(0).getID());
516         mAvrcpStateMachine.requestContents(playerOneNode);
517         verify(mAvrcpControllerService,
518                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).setBrowsedPlayerNative(
519                 eq(mTestAddress), eq(1));
520         mAvrcpStateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_FOLDER_PATH, 5);
521         verify(mAvrcpControllerService,
522                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).getFolderListNative(eq(mTestAddress),
523                 eq(0), eq(4));
524     }
525 
526     /**
527      * Make an AvrcpItem suitable for being included in the Now Playing list for the test device
528      */
makeNowPlayingItem(long uid, String name)529     private AvrcpItem makeNowPlayingItem(long uid, String name) {
530         AvrcpItem.Builder aib = new AvrcpItem.Builder();
531         aib.setDevice(mTestDevice);
532         aib.setItemType(AvrcpItem.TYPE_MEDIA);
533         aib.setType(AvrcpItem.MEDIA_AUDIO);
534         aib.setTitle(name);
535         aib.setUid(uid);
536         aib.setUuid(UUID.randomUUID().toString());
537         aib.setPlayable(true);
538         return aib.build();
539     }
540 
541     /**
542      * Get the current Now Playing list for the test device
543      */
getNowPlayingList()544     private List<AvrcpItem> getNowPlayingList() {
545         BrowseTree.BrowseNode nowPlaying = mAvrcpStateMachine.findNode("NOW_PLAYING");
546         List<AvrcpItem> nowPlayingList = new ArrayList<AvrcpItem>();
547         for (BrowseTree.BrowseNode child : nowPlaying.getChildren()) {
548             nowPlayingList.add(child.mItem);
549         }
550         return nowPlayingList;
551     }
552 
553     /**
554      * Set the current Now Playing list for the test device
555      */
setNowPlayingList(List<AvrcpItem> nowPlayingList)556     private void setNowPlayingList(List<AvrcpItem> nowPlayingList) {
557         BrowseTree.BrowseNode nowPlaying = mAvrcpStateMachine.findNode("NOW_PLAYING");
558         mAvrcpStateMachine.requestContents(nowPlaying);
559         mAvrcpStateMachine.sendMessage(
560                 AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_FOLDER_ITEMS, nowPlayingList);
561         mAvrcpStateMachine.sendMessage(
562                 AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_FOLDER_ITEMS_OUT_OF_RANGE);
563 
564         // Wait for the now playing list to be propagated
565         TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper());
566 
567         // Make sure its set by re grabbing the node and checking its contents are cached
568         nowPlaying = mAvrcpStateMachine.findNode("NOW_PLAYING");
569         Assert.assertTrue(nowPlaying.isCached());
570         assertNowPlayingList(nowPlayingList);
571     }
572 
573     /**
574      * Assert that the Now Playing list is a particular value
575      */
assertNowPlayingList(List<AvrcpItem> expected)576     private void assertNowPlayingList(List<AvrcpItem> expected) {
577         List<AvrcpItem> current = getNowPlayingList();
578         Assert.assertEquals(expected.size(), current.size());
579         for (int i = 0; i < expected.size(); i++) {
580             Assert.assertEquals(expected.get(i), current.get(i));
581         }
582     }
583 
584     /**
585      * Test addressed media player changing to a player we know about
586      * Verify when the addressed media player changes browsing data updates
587      */
588     @Test
testPlayerChanged()589     public void testPlayerChanged() {
590         setUpConnectedState(true, true);
591         final String rootName = "__ROOT__";
592 
593         //Get the root of the device
594         BrowseTree.BrowseNode results = mAvrcpStateMachine.findNode(rootName);
595         Assert.assertEquals(rootName + mTestDevice.toString(), results.getID());
596 
597         //Request fetch the list of players
598         BrowseTree.BrowseNode playerNodes = mAvrcpStateMachine.findNode(results.getID());
599         mAvrcpStateMachine.requestContents(results);
600         verify(mAvrcpControllerService,
601                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).getPlayerListNative(eq(mTestAddress),
602                 eq(0), eq(19));
603 
604         //Provide back two player objects, IDs 1 and 2
605         byte[] playerFeatures =
606                 new byte[]{0, 0, 0, 0, 0, (byte) 0xb7, 0x01, 0x0c, 0x0a, 0, 0, 0, 0, 0, 0, 0};
607         AvrcpPlayer playerOne = new AvrcpPlayer(mTestDevice, 1, "Player 1", playerFeatures, 1, 1);
608         AvrcpPlayer playerTwo = new AvrcpPlayer(mTestDevice, 2, "Player 2", playerFeatures, 1, 1);
609         List<AvrcpPlayer> testPlayers = new ArrayList<>();
610         testPlayers.add(playerOne);
611         testPlayers.add(playerTwo);
612         mAvrcpStateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_PLAYER_ITEMS,
613                 testPlayers);
614 
615         //Set something arbitrary for the current Now Playing list
616         List<AvrcpItem> nowPlayingList = new ArrayList<AvrcpItem>();
617         nowPlayingList.add(makeNowPlayingItem(1, "Song 1"));
618         nowPlayingList.add(makeNowPlayingItem(2, "Song 2"));
619         setNowPlayingList(nowPlayingList);
620 
621         //Change players and verify that BT attempts to update the results
622         mAvrcpStateMachine.sendMessage(
623                 AvrcpControllerStateMachine.MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED, 2);
624         TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper());
625 
626         //Make sure the Now Playing list is now cleared
627         assertNowPlayingList(new ArrayList<AvrcpItem>());
628 
629         //Verify that a player change to a player with Now Playing support causes a refresh. This
630         //should be called twice, once to give data and once to ensure we're out of elements
631         verify(mAvrcpControllerService,
632                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).getNowPlayingListNative(
633                 eq(mTestAddress), eq(0), eq(19));
634     }
635 
636     /**
637      * Test addressed media player change to a player we don't know about
638      * Verify when the addressed media player changes browsing data updates
639      * Verify that the contents of a player are fetched upon request
640      */
641     @Test
testPlayerChangedToUnknownPlayer()642     public void testPlayerChangedToUnknownPlayer() {
643         setUpConnectedState(true, true);
644         final String rootName = "__ROOT__";
645 
646         //Get the root of the device
647         BrowseTree.BrowseNode rootNode = mAvrcpStateMachine.findNode(rootName);
648         Assert.assertEquals(rootName + mTestDevice.toString(), rootNode.getID());
649 
650         //Request fetch the list of players
651         BrowseTree.BrowseNode playerNodes = mAvrcpStateMachine.findNode(rootNode.getID());
652         mAvrcpStateMachine.requestContents(rootNode);
653         verify(mAvrcpControllerService,
654                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).getPlayerListNative(eq(mTestAddress),
655                 eq(0), eq(19));
656 
657         //Provide back a player object
658         byte[] playerFeatures =
659                 new byte[]{0, 0, 0, 0, 0, (byte) 0xb7, 0x01, 0x0c, 0x0a, 0, 0, 0, 0, 0, 0, 0};
660         AvrcpPlayer playerOne = new AvrcpPlayer(mTestDevice, 1, "Player 1", playerFeatures, 1, 1);
661         List<AvrcpPlayer> testPlayers = new ArrayList<>();
662         testPlayers.add(playerOne);
663         mAvrcpStateMachine.sendMessage(
664                 AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_PLAYER_ITEMS, testPlayers);
665 
666         //Set something arbitrary for the current Now Playing list
667         List<AvrcpItem> nowPlayingList = new ArrayList<AvrcpItem>();
668         nowPlayingList.add(makeNowPlayingItem(1, "Song 1"));
669         nowPlayingList.add(makeNowPlayingItem(2, "Song 2"));
670         setNowPlayingList(nowPlayingList);
671 
672         //Change players
673         mAvrcpStateMachine.sendMessage(
674                 AvrcpControllerStateMachine.MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED, 4);
675         TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper());
676 
677         //Make sure the Now Playing list is now cleared
678         assertNowPlayingList(new ArrayList<AvrcpItem>());
679 
680         //Make sure the root node is no longer cached
681         rootNode = mAvrcpStateMachine.findNode(rootName);
682         Assert.assertFalse(rootNode.isCached());
683     }
684 
685     /**
686      * Test that the Now Playing playlist is updated when it changes.
687      */
688     @Test
testNowPlaying()689     public void testNowPlaying() {
690         setUpConnectedState(true, true);
691         mAvrcpStateMachine.nowPlayingContentChanged();
692         verify(mAvrcpControllerService,
693                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).getNowPlayingListNative(
694                 eq(mTestAddress), eq(0), eq(19));
695     }
696 
697     /**
698      * Test that AVRCP events such as playback commands can execute while performing browsing.
699      */
700     @Test
testPlayWhileBrowsing()701     public void testPlayWhileBrowsing() {
702         setUpConnectedState(true, true);
703         final String rootName = "__ROOT__";
704         final String playerName = "Player 1";
705 
706         //Get the root of the device
707         BrowseTree.BrowseNode results = mAvrcpStateMachine.findNode(rootName);
708         Assert.assertEquals(rootName + mTestDevice.toString(), results.getID());
709 
710         //Request fetch the list of players
711         BrowseTree.BrowseNode playerNodes = mAvrcpStateMachine.findNode(results.getID());
712         mAvrcpStateMachine.requestContents(results);
713 
714         MediaControllerCompat.TransportControls transportControls =
715                 BluetoothMediaBrowserService.getTransportControls();
716         transportControls.play();
717         verify(mAvrcpControllerService,
718                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
719                 eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_PLAY), eq(KEY_DOWN));
720         verify(mAvrcpControllerService,
721                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
722                 eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_PLAY), eq(KEY_UP));
723     }
724 
725     /**
726      * Test that Absolute Volume Registration is working
727      */
728     @Test
testRegisterAbsVolumeNotification()729     public void testRegisterAbsVolumeNotification() {
730         setUpConnectedState(true, true);
731         mAvrcpStateMachine.sendMessage(
732                 AvrcpControllerStateMachine.MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION);
733         verify(mAvrcpControllerService, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1))
734                 .sendRegisterAbsVolRspNative(any(), anyByte(), eq(127), anyInt());
735     }
736 
737     /**
738      * Test playback does not request focus when another app is playing music.
739      */
740     @Test
testPlaybackWhileMusicPlaying()741     public void testPlaybackWhileMusicPlaying() {
742         when(mMockResources.getBoolean(R.bool.a2dp_sink_automatically_request_audio_focus))
743                 .thenReturn(false);
744         when(mA2dpSinkService.getFocusState()).thenReturn(AudioManager.AUDIOFOCUS_NONE);
745         doReturn(true).when(mAudioManager).isMusicActive();
746         setUpConnectedState(true, true);
747         mAvrcpStateMachine.sendMessage(
748                 AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_STATUS_CHANGED,
749                 PlaybackStateCompat.STATE_PLAYING);
750         TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper());
751         verify(mAvrcpControllerService,
752                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
753                 eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE), eq(KEY_DOWN));
754         verify(mA2dpSinkService, never()).requestAudioFocus(mTestDevice, true);
755     }
756 
757     /**
758      * Test playback requests focus while nothing is playing music.
759      */
760     @Test
testPlaybackWhileIdle()761     public void testPlaybackWhileIdle() {
762         when(mA2dpSinkService.getFocusState()).thenReturn(AudioManager.AUDIOFOCUS_NONE);
763         doReturn(false).when(mAudioManager).isMusicActive();
764         setUpConnectedState(true, true);
765         mAvrcpStateMachine.sendMessage(
766                 AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_STATUS_CHANGED,
767                 PlaybackStateCompat.STATE_PLAYING);
768         TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper());
769         verify(mA2dpSinkService).requestAudioFocus(mTestDevice, true);
770     }
771 
772     /**
773      * Test receiving a playback status of playing while we're in an error state
774      * as it relates to getting audio focus.
775      *
776      * Verify we send a pause command and never attempt to request audio focus
777      */
778     @Test
testPlaybackWhileErrorState()779     public void testPlaybackWhileErrorState() {
780         when(mA2dpSinkService.getFocusState()).thenReturn(AudioManager.ERROR);
781         setUpConnectedState(true, true);
782         mAvrcpStateMachine.sendMessage(
783                 AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_STATUS_CHANGED,
784                 PlaybackStateCompat.STATE_PLAYING);
785         TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper());
786         verify(mAvrcpControllerService,
787                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
788                 eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE), eq(KEY_DOWN));
789         verify(mA2dpSinkService, never()).requestAudioFocus(mTestDevice, true);
790     }
791 
792     /**
793      * Test receiving a playback status of playing while we have focus
794      *
795      * Verify we do not send a pause command and never attempt to request audio focus
796      */
797     @Test
testPlaybackWhilePlayingState()798     public void testPlaybackWhilePlayingState() {
799         when(mA2dpSinkService.getFocusState()).thenReturn(AudioManager.AUDIOFOCUS_GAIN);
800         setUpConnectedState(true, true);
801         Assert.assertTrue(mAvrcpStateMachine.isActive());
802         mAvrcpStateMachine.sendMessage(
803                 AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_STATUS_CHANGED,
804                 PlaybackStateCompat.STATE_PLAYING);
805         TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper());
806         verify(mAvrcpControllerService, never()).sendPassThroughCommandNative(
807                 eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE), eq(KEY_DOWN));
808         verify(mA2dpSinkService, never()).requestAudioFocus(mTestDevice, true);
809     }
810 
811     /**
812      * Test receiving a playback status of playing from a device that isn't active
813      *
814      * Verify we do not send a pause command and never attempt to request audio focus
815      */
816     @Test
testPlaybackWhileNotActiveDevice()817     public void testPlaybackWhileNotActiveDevice() {
818         byte[] secondTestAddress = new byte[]{00, 01, 02, 03, 04, 06};
819         BluetoothDevice secondTestDevice = mAdapter.getRemoteDevice(secondTestAddress);
820         AvrcpControllerStateMachine secondAvrcpStateMachine =
821                 new AvrcpControllerStateMachine(secondTestDevice, mAvrcpControllerService);
822         secondAvrcpStateMachine.start();
823 
824         setUpConnectedState(true, true);
825         secondAvrcpStateMachine.connect(StackEvent.connectionStateChanged(true, true));
826         TestUtils.waitForLooperToFinishScheduledTask(secondAvrcpStateMachine.getHandler()
827                 .getLooper());
828 
829         Assert.assertTrue(secondAvrcpStateMachine.isActive());
830         Assert.assertFalse(mAvrcpStateMachine.isActive());
831 
832         mAvrcpStateMachine.sendMessage(
833                 AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_STATUS_CHANGED,
834                 PlaybackStateCompat.STATE_PLAYING);
835         TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper());
836         verify(mAvrcpControllerService,
837                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
838                 eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE), eq(KEY_DOWN));
839         verify(mA2dpSinkService, never()).requestAudioFocus(mTestDevice, true);
840 
841         secondAvrcpStateMachine.disconnect();
842         TestUtils.waitForLooperToFinishScheduledTask(secondAvrcpStateMachine.getHandler()
843                 .getLooper());
844         Assert.assertFalse(secondAvrcpStateMachine.isActive());
845         Assert.assertFalse(mAvrcpStateMachine.isActive());
846     }
847 
848     /**
849      * Test that the correct device becomes active
850      *
851      * The first connected device is automatically active, additional ones are not.
852      * After an explicit play command a device becomes active.
853      */
854     @Test
testActiveDeviceManagement()855     public void testActiveDeviceManagement() {
856         // Setup structures and verify initial conditions
857         final String rootName = "__ROOT__";
858         final String playerName = "Player 1";
859         byte[] secondTestAddress = new byte[]{00, 01, 02, 03, 04, 06};
860         BluetoothDevice secondTestDevice = mAdapter.getRemoteDevice(secondTestAddress);
861         AvrcpControllerStateMachine secondAvrcpStateMachine =
862                 new AvrcpControllerStateMachine(secondTestDevice, mAvrcpControllerService);
863         secondAvrcpStateMachine.start();
864         Assert.assertFalse(mAvrcpStateMachine.isActive());
865 
866         // Connect device 1 and 2 and verify second one is set as active
867         setUpConnectedState(true, true);
868         TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper());
869         Assert.assertTrue(mAvrcpStateMachine.isActive());
870 
871         secondAvrcpStateMachine.connect(StackEvent.connectionStateChanged(true, true));
872         TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper());
873         TestUtils.waitForLooperToFinishScheduledTask(secondAvrcpStateMachine.getHandler()
874                 .getLooper());
875 
876         Assert.assertFalse(mAvrcpStateMachine.isActive());
877         Assert.assertTrue(secondAvrcpStateMachine.isActive());
878 
879         // Request the second device to play an item and verify active device switched
880         BrowseTree.BrowseNode results = mAvrcpStateMachine.findNode(rootName);
881         Assert.assertEquals(rootName + mTestDevice.toString(), results.getID());
882         BrowseTree.BrowseNode playerNodes = mAvrcpStateMachine.findNode(results.getID());
883         secondAvrcpStateMachine.playItem(playerNodes);
884         TestUtils.waitForLooperToFinishScheduledTask(secondAvrcpStateMachine.getHandler()
885                 .getLooper());
886         Assert.assertFalse(mAvrcpStateMachine.isActive());
887         Assert.assertTrue(secondAvrcpStateMachine.isActive());
888 
889         secondAvrcpStateMachine.disconnect();
890         TestUtils.waitForLooperToFinishScheduledTask(secondAvrcpStateMachine.getHandler()
891                 .getLooper());
892         Assert.assertFalse(secondAvrcpStateMachine.isActive());
893         Assert.assertFalse(mAvrcpStateMachine.isActive());
894     }
895 
896     /**
897      * Setup Connected State
898      *
899      * @return number of times mAvrcpControllerService.sendBroadcastAsUser() has been invoked
900      */
setUpConnectedState(boolean control, boolean browsing)901     private int setUpConnectedState(boolean control, boolean browsing) {
902         // Put test state machine into connected state
903         Assert.assertThat(mAvrcpStateMachine.getCurrentState(),
904                 IsInstanceOf.instanceOf(AvrcpControllerStateMachine.Disconnected.class));
905 
906         mAvrcpStateMachine.connect(StackEvent.connectionStateChanged(control, browsing));
907         TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper());
908         verify(mAvrcpControllerService, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcast(
909                 mIntentArgument.capture(), eq(ProfileService.BLUETOOTH_PERM));
910         Assert.assertThat(mAvrcpStateMachine.getCurrentState(),
911                 IsInstanceOf.instanceOf(AvrcpControllerStateMachine.Connected.class));
912         Assert.assertEquals(mAvrcpStateMachine.getState(), BluetoothProfile.STATE_CONNECTED);
913 
914         return BluetoothProfile.STATE_CONNECTED;
915     }
916 
917 }
918