blob: 541eb9dbd14aa6037f4adfde4954fe0202b535db [file] [log] [blame]
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.bluetooth.avrcpcontroller;
import static org.mockito.Mockito.*;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothAvrcpController;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.media.AudioManager;
import android.os.Looper;
import android.support.v4.media.session.MediaControllerCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.FlakyTest;
import androidx.test.filters.MediumTest;
import androidx.test.rule.ServiceTestRule;
import androidx.test.runner.AndroidJUnit4;
import com.android.bluetooth.R;
import com.android.bluetooth.TestUtils;
import com.android.bluetooth.a2dpsink.A2dpSinkService;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.ProfileService;
import org.hamcrest.core.IsInstanceOf;
import org.junit.After;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@MediumTest
@RunWith(AndroidJUnit4.class)
public class AvrcpControllerStateMachineTest {
private static final int ASYNC_CALL_TIMEOUT_MILLIS = 100;
private static final int CONNECT_TIMEOUT_TEST_MILLIS = 1000;
private static final int KEY_DOWN = 0;
private static final int KEY_UP = 1;
private AvrcpControllerStateMachine mAvrcpStateMachine;
private BluetoothAdapter mAdapter;
private Context mTargetContext;
private BluetoothDevice mTestDevice;
private ArgumentCaptor<Intent> mIntentArgument = ArgumentCaptor.forClass(Intent.class);
private byte[] mTestAddress = new byte[]{00, 01, 02, 03, 04, 05};
@Rule public final ServiceTestRule mAvrcpServiceRule = new ServiceTestRule();
@Rule public final ServiceTestRule mA2dpServiceRule = new ServiceTestRule();
@Mock
private AdapterService mAvrcpAdapterService;
@Mock
private AdapterService mA2dpAdapterService;
@Mock
private AudioManager mAudioManager;
@Mock
private AvrcpControllerService mAvrcpControllerService;
@Mock
private A2dpSinkService mA2dpSinkService;
@Mock
private Resources mMockResources;
@Before
public void setUp() throws Exception {
mTargetContext = InstrumentationRegistry.getTargetContext();
Assume.assumeTrue("Ignore test when AVRCP Controller is not enabled",
mTargetContext.getResources().getBoolean(
R.bool.profile_supported_avrcp_controller));
if (Looper.myLooper() == null) {
Looper.prepare();
}
Assert.assertNotNull(Looper.myLooper());
// Setup mocks and test assets
MockitoAnnotations.initMocks(this);
TestUtils.setAdapterService(mAvrcpAdapterService);
TestUtils.startService(mAvrcpServiceRule, AvrcpControllerService.class);
TestUtils.clearAdapterService(mAvrcpAdapterService);
TestUtils.setAdapterService(mA2dpAdapterService);
TestUtils.startService(mA2dpServiceRule, A2dpSinkService.class);
when(mA2dpSinkService.setActiveDeviceNative(any())).thenReturn(true);
when(mMockResources.getBoolean(R.bool.a2dp_sink_automatically_request_audio_focus))
.thenReturn(true);
doReturn(mMockResources).when(mAvrcpControllerService).getResources();
A2dpSinkService.setA2dpSinkService(mA2dpSinkService);
doReturn(15).when(mAudioManager).getStreamMaxVolume(anyInt());
doReturn(8).when(mAudioManager).getStreamVolume(anyInt());
doReturn(true).when(mAudioManager).isVolumeFixed();
doReturn(mAudioManager).when(mAvrcpControllerService)
.getSystemService(Context.AUDIO_SERVICE);
// This line must be called to make sure relevant objects are initialized properly
mAdapter = BluetoothAdapter.getDefaultAdapter();
// Get a device for testing
mTestDevice = mAdapter.getRemoteDevice(mTestAddress);
mAvrcpControllerService.start();
mAvrcpControllerService.sBrowseTree = new BrowseTree(null);
mAvrcpStateMachine = new AvrcpControllerStateMachine(mTestDevice, mAvrcpControllerService);
}
@After
public void tearDown() throws Exception {
if (!mTargetContext.getResources().getBoolean(R.bool.profile_supported_avrcp_controller)) {
return;
}
TestUtils.clearAdapterService(mA2dpAdapterService);
}
/**
* Test to confirm that the state machine is capable of cycling through the 4
* connection states, and that upon completion, it cleans up aftwards.
*/
@Test
public void testDisconnect() {
int numBroadcastsSent = setUpConnectedState(true, true);
StackEvent event =
StackEvent.connectionStateChanged(false, false);
mAvrcpStateMachine.disconnect();
numBroadcastsSent += 2;
verify(mAvrcpControllerService,
timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcast(
mIntentArgument.capture(), eq(ProfileService.BLUETOOTH_PERM));
Assert.assertEquals(mTestDevice, mIntentArgument.getValue().getParcelableExtra(
BluetoothDevice.EXTRA_DEVICE));
Assert.assertEquals(BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED,
mIntentArgument.getValue().getAction());
Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
mIntentArgument.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
Assert.assertThat(mAvrcpStateMachine.getCurrentState(),
IsInstanceOf.instanceOf(AvrcpControllerStateMachine.Disconnected.class));
Assert.assertEquals(mAvrcpStateMachine.getState(), BluetoothProfile.STATE_DISCONNECTED);
verify(mAvrcpControllerService).removeStateMachine(eq(mAvrcpStateMachine));
MediaControllerCompat.TransportControls transportControls =
BluetoothMediaBrowserService.getTransportControls();
Assert.assertEquals(PlaybackStateCompat.STATE_ERROR,
BluetoothMediaBrowserService.getPlaybackState());
}
/**
* Test to confirm that a control only device can be established (no browsing)
*/
@Test
public void testControlOnly() {
int numBroadcastsSent = setUpConnectedState(true, false);
MediaControllerCompat.TransportControls transportControls =
BluetoothMediaBrowserService.getTransportControls();
Assert.assertNotNull(transportControls);
Assert.assertEquals(PlaybackStateCompat.STATE_NONE,
BluetoothMediaBrowserService.getPlaybackState());
StackEvent event =
StackEvent.connectionStateChanged(false, false);
mAvrcpStateMachine.disconnect();
numBroadcastsSent += 2;
verify(mAvrcpControllerService,
timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcast(
mIntentArgument.capture(), eq(ProfileService.BLUETOOTH_PERM));
Assert.assertEquals(mTestDevice, mIntentArgument.getValue().getParcelableExtra(
BluetoothDevice.EXTRA_DEVICE));
Assert.assertEquals(BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED,
mIntentArgument.getValue().getAction());
Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
mIntentArgument.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
Assert.assertThat(mAvrcpStateMachine.getCurrentState(),
IsInstanceOf.instanceOf(AvrcpControllerStateMachine.Disconnected.class));
Assert.assertEquals(mAvrcpStateMachine.getState(), BluetoothProfile.STATE_DISCONNECTED);
verify(mAvrcpControllerService).removeStateMachine(eq(mAvrcpStateMachine));
Assert.assertEquals(PlaybackStateCompat.STATE_ERROR,
BluetoothMediaBrowserService.getPlaybackState());
}
/**
* Test to confirm that a browsing only device can be established (no control)
*/
@Test
@FlakyTest
public void testBrowsingOnly() {
Assert.assertEquals(0, mAvrcpControllerService.sBrowseTree.mRootNode.getChildrenCount());
int numBroadcastsSent = setUpConnectedState(false, true);
Assert.assertEquals(1, mAvrcpControllerService.sBrowseTree.mRootNode.getChildrenCount());
Assert.assertEquals(PlaybackStateCompat.STATE_NONE,
BluetoothMediaBrowserService.getPlaybackState());
StackEvent event =
StackEvent.connectionStateChanged(false, false);
mAvrcpStateMachine.disconnect();
numBroadcastsSent += 2;
verify(mAvrcpControllerService,
timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcast(
mIntentArgument.capture(), eq(ProfileService.BLUETOOTH_PERM));
Assert.assertEquals(mTestDevice, mIntentArgument.getValue().getParcelableExtra(
BluetoothDevice.EXTRA_DEVICE));
Assert.assertEquals(BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED,
mIntentArgument.getValue().getAction());
Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
mIntentArgument.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
Assert.assertThat(mAvrcpStateMachine.getCurrentState(),
IsInstanceOf.instanceOf(AvrcpControllerStateMachine.Disconnected.class));
Assert.assertEquals(mAvrcpStateMachine.getState(), BluetoothProfile.STATE_DISCONNECTED);
verify(mAvrcpControllerService).removeStateMachine(eq(mAvrcpStateMachine));
MediaControllerCompat.TransportControls transportControls =
BluetoothMediaBrowserService.getTransportControls();
Assert.assertEquals(PlaybackStateCompat.STATE_ERROR,
BluetoothMediaBrowserService.getPlaybackState());
}
/**
* Test to make sure the state machine is tracking the correct device
*/
@Test
public void testGetDevice() {
Assert.assertEquals(mAvrcpStateMachine.getDevice(), mTestDevice);
}
/**
* Test that dumpsys will generate information about connected devices
*/
@Test
public void testDump() {
StringBuilder sb = new StringBuilder();
mAvrcpStateMachine.dump(sb);
Assert.assertEquals(sb.toString(),
" mDevice: " + mTestDevice.toString()
+ "(null) name=AvrcpControllerStateMachine state=(null)\n"
+ " isActive: false\n");
}
/**
* Test media browser play command
*/
@Test
public void testPlay() throws Exception {
setUpConnectedState(true, true);
MediaControllerCompat.TransportControls transportControls =
BluetoothMediaBrowserService.getTransportControls();
//Play
transportControls.play();
verify(mAvrcpControllerService,
timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_PLAY), eq(KEY_DOWN));
verify(mAvrcpControllerService,
timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_PLAY), eq(KEY_UP));
}
/**
* Test media browser pause command
*/
@Test
public void testPause() throws Exception {
setUpConnectedState(true, true);
MediaControllerCompat.TransportControls transportControls =
BluetoothMediaBrowserService.getTransportControls();
//Pause
transportControls.pause();
verify(mAvrcpControllerService,
timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE), eq(KEY_DOWN));
verify(mAvrcpControllerService,
timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE), eq(KEY_UP));
}
/**
* Test media browser stop command
*/
@Test
public void testStop() throws Exception {
setUpConnectedState(true, true);
MediaControllerCompat.TransportControls transportControls =
BluetoothMediaBrowserService.getTransportControls();
//Stop
transportControls.stop();
verify(mAvrcpControllerService,
timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_STOP), eq(KEY_DOWN));
verify(mAvrcpControllerService,
timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_STOP), eq(KEY_UP));
}
/**
* Test media browser next command
*/
@Test
public void testNext() throws Exception {
setUpConnectedState(true, true);
MediaControllerCompat.TransportControls transportControls =
BluetoothMediaBrowserService.getTransportControls();
//Next
transportControls.skipToNext();
verify(mAvrcpControllerService,
timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_FORWARD),
eq(KEY_DOWN));
verify(mAvrcpControllerService,
timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_FORWARD), eq(KEY_UP));
}
/**
* Test media browser previous command
*/
@Test
public void testPrevious() throws Exception {
setUpConnectedState(true, true);
MediaControllerCompat.TransportControls transportControls =
BluetoothMediaBrowserService.getTransportControls();
//Previous
transportControls.skipToPrevious();
verify(mAvrcpControllerService,
timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_BACKWARD),
eq(KEY_DOWN));
verify(mAvrcpControllerService,
timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_BACKWARD), eq(KEY_UP));
}
/**
* Test media browser fast forward command
*/
@Test
@FlakyTest
public void testFastForward() throws Exception {
setUpConnectedState(true, true);
MediaControllerCompat.TransportControls transportControls =
BluetoothMediaBrowserService.getTransportControls();
//FastForward
transportControls.fastForward();
verify(mAvrcpControllerService,
timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_FF), eq(KEY_DOWN));
//Finish FastForwarding
transportControls.play();
verify(mAvrcpControllerService,
timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_FF), eq(KEY_UP));
}
/**
* Test media browser rewind command
*/
@Test
public void testRewind() throws Exception {
setUpConnectedState(true, true);
MediaControllerCompat.TransportControls transportControls =
BluetoothMediaBrowserService.getTransportControls();
//Rewind
transportControls.rewind();
verify(mAvrcpControllerService,
timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_REWIND), eq(KEY_DOWN));
//Finish Rewinding
transportControls.play();
verify(mAvrcpControllerService,
timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_REWIND), eq(KEY_UP));
}
/**
* Test media browser skip to queue item
*/
@Test
public void testSkipToQueueInvalid() throws Exception {
byte scope = 1;
int minSize = 0;
int maxSize = 255;
setUpConnectedState(true, true);
MediaControllerCompat.TransportControls transportControls =
BluetoothMediaBrowserService.getTransportControls();
//Play an invalid item below start
transportControls.skipToQueueItem(minSize - 1);
verify(mAvrcpControllerService,
timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(0)).playItemNative(
eq(mTestAddress), eq(scope), anyLong(), anyInt());
//Play an invalid item beyond end
transportControls.skipToQueueItem(maxSize + 1);
verify(mAvrcpControllerService,
timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(0)).playItemNative(
eq(mTestAddress), eq(scope), anyLong(), anyInt());
}
/**
* Test media browser shuffle command
*/
@Test
public void testShuffle() throws Exception {
byte[] shuffleSetting = new byte[]{3};
byte[] shuffleMode = new byte[]{2};
setUpConnectedState(true, true);
MediaControllerCompat.TransportControls transportControls =
BluetoothMediaBrowserService.getTransportControls();
//Shuffle
transportControls.setShuffleMode(1);
verify(mAvrcpControllerService, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1))
.setPlayerApplicationSettingValuesNative(
eq(mTestAddress), eq((byte) 1), eq(shuffleSetting), eq(shuffleMode));
}
/**
* Test media browser repeat command
*/
@Test
public void testRepeat() throws Exception {
byte[] repeatSetting = new byte[]{2};
byte[] repeatMode = new byte[]{3};
setUpConnectedState(true, true);
MediaControllerCompat.TransportControls transportControls =
BluetoothMediaBrowserService.getTransportControls();
//Shuffle
transportControls.setRepeatMode(2);
verify(mAvrcpControllerService, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1))
.setPlayerApplicationSettingValuesNative(
eq(mTestAddress), eq((byte) 1), eq(repeatSetting), eq(repeatMode));
}
/**
* Test media browsing
* Verify that a browse tree is created with the proper root
* Verify that a player can be fetched and added to the browse tree
* Verify that the contents of a player are fetched upon request
*/
@Test
@FlakyTest
public void testBrowsingCommands() {
setUpConnectedState(true, true);
final String rootName = "__ROOT__";
final String playerName = "Player 1";
//Get the root of the device
BrowseTree.BrowseNode results = mAvrcpStateMachine.findNode(rootName);
Assert.assertEquals(rootName + mTestDevice.toString(), results.getID());
//Request fetch the list of players
BrowseTree.BrowseNode playerNodes = mAvrcpStateMachine.findNode(results.getID());
mAvrcpStateMachine.requestContents(results);
verify(mAvrcpControllerService,
timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).getPlayerListNative(eq(mTestAddress),
eq(0), eq(19));
//Provide back a player object
byte[] playerFeatures =
new byte[]{0, 0, 0, 0, 0, (byte) 0xb7, 0x01, 0x0c, 0x0a, 0, 0, 0, 0, 0, 0, 0};
AvrcpPlayer playerOne = new AvrcpPlayer(mTestDevice, 1, playerName, playerFeatures, 1, 1);
List<AvrcpPlayer> testPlayers = new ArrayList<>();
testPlayers.add(playerOne);
mAvrcpStateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_PLAYER_ITEMS,
testPlayers);
//Verify that the player object is available.
mAvrcpStateMachine.requestContents(results);
verify(mAvrcpControllerService,
timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).getPlayerListNative(eq(mTestAddress),
eq(1), eq(0));
mAvrcpStateMachine.sendMessage(
AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_FOLDER_ITEMS_OUT_OF_RANGE);
playerNodes = mAvrcpStateMachine.findNode(results.getID());
Assert.assertEquals(true, results.isCached());
Assert.assertEquals("MediaItem{mFlags=1, mDescription=" + playerName + ", null, null}",
results.getChildren().get(0).getMediaItem().toString());
//Fetch contents of that player object
BrowseTree.BrowseNode playerOneNode = mAvrcpStateMachine.findNode(
results.getChildren().get(0).getID());
mAvrcpStateMachine.requestContents(playerOneNode);
verify(mAvrcpControllerService,
timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).setBrowsedPlayerNative(
eq(mTestAddress), eq(1));
mAvrcpStateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_FOLDER_PATH, 5);
verify(mAvrcpControllerService,
timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).getFolderListNative(eq(mTestAddress),
eq(0), eq(4));
}
/**
* Make an AvrcpItem suitable for being included in the Now Playing list for the test device
*/
private AvrcpItem makeNowPlayingItem(long uid, String name) {
AvrcpItem.Builder aib = new AvrcpItem.Builder();
aib.setDevice(mTestDevice);
aib.setItemType(AvrcpItem.TYPE_MEDIA);
aib.setType(AvrcpItem.MEDIA_AUDIO);
aib.setTitle(name);
aib.setUid(uid);
aib.setUuid(UUID.randomUUID().toString());
aib.setPlayable(true);
return aib.build();
}
/**
* Get the current Now Playing list for the test device
*/
private List<AvrcpItem> getNowPlayingList() {
BrowseTree.BrowseNode nowPlaying = mAvrcpStateMachine.findNode("NOW_PLAYING");
List<AvrcpItem> nowPlayingList = new ArrayList<AvrcpItem>();
for (BrowseTree.BrowseNode child : nowPlaying.getChildren()) {
nowPlayingList.add(child.mItem);
}
return nowPlayingList;
}
/**
* Set the current Now Playing list for the test device
*/
private void setNowPlayingList(List<AvrcpItem> nowPlayingList) {
BrowseTree.BrowseNode nowPlaying = mAvrcpStateMachine.findNode("NOW_PLAYING");
mAvrcpStateMachine.requestContents(nowPlaying);
mAvrcpStateMachine.sendMessage(
AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_FOLDER_ITEMS, nowPlayingList);
mAvrcpStateMachine.sendMessage(
AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_FOLDER_ITEMS_OUT_OF_RANGE);
// Wait for the now playing list to be propagated
TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper());
// Make sure its set by re grabbing the node and checking its contents are cached
nowPlaying = mAvrcpStateMachine.findNode("NOW_PLAYING");
Assert.assertTrue(nowPlaying.isCached());
assertNowPlayingList(nowPlayingList);
}
/**
* Assert that the Now Playing list is a particular value
*/
private void assertNowPlayingList(List<AvrcpItem> expected) {
List<AvrcpItem> current = getNowPlayingList();
Assert.assertEquals(expected.size(), current.size());
for (int i = 0; i < expected.size(); i++) {
Assert.assertEquals(expected.get(i), current.get(i));
}
}
/**
* Test addressed media player changing to a player we know about
* Verify when the addressed media player changes browsing data updates
*/
@Test
public void testPlayerChanged() {
setUpConnectedState(true, true);
final String rootName = "__ROOT__";
//Get the root of the device
BrowseTree.BrowseNode results = mAvrcpStateMachine.findNode(rootName);
Assert.assertEquals(rootName + mTestDevice.toString(), results.getID());
//Request fetch the list of players
BrowseTree.BrowseNode playerNodes = mAvrcpStateMachine.findNode(results.getID());
mAvrcpStateMachine.requestContents(results);
verify(mAvrcpControllerService,
timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).getPlayerListNative(eq(mTestAddress),
eq(0), eq(19));
//Provide back two player objects, IDs 1 and 2
byte[] playerFeatures =
new byte[]{0, 0, 0, 0, 0, (byte) 0xb7, 0x01, 0x0c, 0x0a, 0, 0, 0, 0, 0, 0, 0};
AvrcpPlayer playerOne = new AvrcpPlayer(mTestDevice, 1, "Player 1", playerFeatures, 1, 1);
AvrcpPlayer playerTwo = new AvrcpPlayer(mTestDevice, 2, "Player 2", playerFeatures, 1, 1);
List<AvrcpPlayer> testPlayers = new ArrayList<>();
testPlayers.add(playerOne);
testPlayers.add(playerTwo);
mAvrcpStateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_PLAYER_ITEMS,
testPlayers);
//Set something arbitrary for the current Now Playing list
List<AvrcpItem> nowPlayingList = new ArrayList<AvrcpItem>();
nowPlayingList.add(makeNowPlayingItem(1, "Song 1"));
nowPlayingList.add(makeNowPlayingItem(2, "Song 2"));
setNowPlayingList(nowPlayingList);
//Change players and verify that BT attempts to update the results
mAvrcpStateMachine.sendMessage(
AvrcpControllerStateMachine.MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED, 2);
TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper());
//Make sure the Now Playing list is now cleared
assertNowPlayingList(new ArrayList<AvrcpItem>());
//Verify that a player change to a player with Now Playing support causes a refresh. This
//should be called twice, once to give data and once to ensure we're out of elements
verify(mAvrcpControllerService,
timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).getNowPlayingListNative(
eq(mTestAddress), eq(0), eq(19));
}
/**
* Test addressed media player change to a player we don't know about
* Verify when the addressed media player changes browsing data updates
* Verify that the contents of a player are fetched upon request
*/
@Test
public void testPlayerChangedToUnknownPlayer() {
setUpConnectedState(true, true);
final String rootName = "__ROOT__";
//Get the root of the device
BrowseTree.BrowseNode rootNode = mAvrcpStateMachine.findNode(rootName);
Assert.assertEquals(rootName + mTestDevice.toString(), rootNode.getID());
//Request fetch the list of players
BrowseTree.BrowseNode playerNodes = mAvrcpStateMachine.findNode(rootNode.getID());
mAvrcpStateMachine.requestContents(rootNode);
verify(mAvrcpControllerService,
timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).getPlayerListNative(eq(mTestAddress),
eq(0), eq(19));
//Provide back a player object
byte[] playerFeatures =
new byte[]{0, 0, 0, 0, 0, (byte) 0xb7, 0x01, 0x0c, 0x0a, 0, 0, 0, 0, 0, 0, 0};
AvrcpPlayer playerOne = new AvrcpPlayer(mTestDevice, 1, "Player 1", playerFeatures, 1, 1);
List<AvrcpPlayer> testPlayers = new ArrayList<>();
testPlayers.add(playerOne);
mAvrcpStateMachine.sendMessage(
AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_PLAYER_ITEMS, testPlayers);
//Set something arbitrary for the current Now Playing list
List<AvrcpItem> nowPlayingList = new ArrayList<AvrcpItem>();
nowPlayingList.add(makeNowPlayingItem(1, "Song 1"));
nowPlayingList.add(makeNowPlayingItem(2, "Song 2"));
setNowPlayingList(nowPlayingList);
//Change players
mAvrcpStateMachine.sendMessage(
AvrcpControllerStateMachine.MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED, 4);
TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper());
//Make sure the Now Playing list is now cleared
assertNowPlayingList(new ArrayList<AvrcpItem>());
//Make sure the root node is no longer cached
rootNode = mAvrcpStateMachine.findNode(rootName);
Assert.assertFalse(rootNode.isCached());
}
/**
* Test that the Now Playing playlist is updated when it changes.
*/
@Test
public void testNowPlaying() {
setUpConnectedState(true, true);
mAvrcpStateMachine.nowPlayingContentChanged();
verify(mAvrcpControllerService,
timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).getNowPlayingListNative(
eq(mTestAddress), eq(0), eq(19));
}
/**
* Test that AVRCP events such as playback commands can execute while performing browsing.
*/
@Test
public void testPlayWhileBrowsing() {
setUpConnectedState(true, true);
final String rootName = "__ROOT__";
final String playerName = "Player 1";
//Get the root of the device
BrowseTree.BrowseNode results = mAvrcpStateMachine.findNode(rootName);
Assert.assertEquals(rootName + mTestDevice.toString(), results.getID());
//Request fetch the list of players
BrowseTree.BrowseNode playerNodes = mAvrcpStateMachine.findNode(results.getID());
mAvrcpStateMachine.requestContents(results);
MediaControllerCompat.TransportControls transportControls =
BluetoothMediaBrowserService.getTransportControls();
transportControls.play();
verify(mAvrcpControllerService,
timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_PLAY), eq(KEY_DOWN));
verify(mAvrcpControllerService,
timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_PLAY), eq(KEY_UP));
}
/**
* Test that Absolute Volume Registration is working
*/
@Test
public void testRegisterAbsVolumeNotification() {
setUpConnectedState(true, true);
mAvrcpStateMachine.sendMessage(
AvrcpControllerStateMachine.MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION);
verify(mAvrcpControllerService, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1))
.sendRegisterAbsVolRspNative(any(), anyByte(), eq(127), anyInt());
}
/**
* Test playback does not request focus when another app is playing music.
*/
@Test
public void testPlaybackWhileMusicPlaying() {
when(mMockResources.getBoolean(R.bool.a2dp_sink_automatically_request_audio_focus))
.thenReturn(false);
Assert.assertEquals(AudioManager.AUDIOFOCUS_NONE, A2dpSinkService.getFocusState());
doReturn(true).when(mAudioManager).isMusicActive();
setUpConnectedState(true, true);
mAvrcpStateMachine.sendMessage(
AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_STATUS_CHANGED,
PlaybackStateCompat.STATE_PLAYING);
TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper());
verify(mAvrcpControllerService,
timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE), eq(KEY_DOWN));
TestUtils.waitForLooperToFinishScheduledTask(
A2dpSinkService.getA2dpSinkService().getMainLooper());
Assert.assertEquals(AudioManager.AUDIOFOCUS_NONE, mA2dpSinkService.getFocusState());
}
/**
* Test playback requests focus while nothing is playing music.
*/
@Test
public void testPlaybackWhileIdle() {
Assert.assertEquals(AudioManager.AUDIOFOCUS_NONE, A2dpSinkService.getFocusState());
doReturn(false).when(mAudioManager).isMusicActive();
setUpConnectedState(true, true);
mAvrcpStateMachine.sendMessage(
AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_STATUS_CHANGED,
PlaybackStateCompat.STATE_PLAYING);
TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper());
TestUtils.waitForLooperToFinishScheduledTask(
mA2dpSinkService.getMainLooper());
verify(mA2dpSinkService).requestAudioFocus(mTestDevice, true);
}
/**
* Test that the correct device becomes active
*
* The first connected device is automatically active, additional ones are not.
* After an explicit play command a device becomes active.
*/
@Test
public void testActiveDeviceManagement() {
// Setup structures and verify initial conditions
final String rootName = "__ROOT__";
final String playerName = "Player 1";
byte[] secondTestAddress = new byte[]{00, 01, 02, 03, 04, 06};
BluetoothDevice secondTestDevice = mAdapter.getRemoteDevice(secondTestAddress);
AvrcpControllerStateMachine secondAvrcpStateMachine =
new AvrcpControllerStateMachine(secondTestDevice, mAvrcpControllerService);
secondAvrcpStateMachine.start();
Assert.assertFalse(mAvrcpStateMachine.isActive());
// Connect device 1 and 2 and verify first one is set as active
setUpConnectedState(true, true);
secondAvrcpStateMachine.connect(StackEvent.connectionStateChanged(true, true));
Assert.assertTrue(mAvrcpStateMachine.isActive());
Assert.assertFalse(secondAvrcpStateMachine.isActive());
// Request the second device to play an item and verify active device switched
BrowseTree.BrowseNode results = mAvrcpStateMachine.findNode(rootName);
Assert.assertEquals(rootName + mTestDevice.toString(), results.getID());
BrowseTree.BrowseNode playerNodes = mAvrcpStateMachine.findNode(results.getID());
secondAvrcpStateMachine.playItem(playerNodes);
TestUtils.waitForLooperToFinishScheduledTask(secondAvrcpStateMachine.getHandler()
.getLooper());
Assert.assertFalse(mAvrcpStateMachine.isActive());
Assert.assertTrue(secondAvrcpStateMachine.isActive());
}
/**
* Setup Connected State
*
* @return number of times mAvrcpControllerService.sendBroadcastAsUser() has been invoked
*/
private int setUpConnectedState(boolean control, boolean browsing) {
// Put test state machine into connected state
mAvrcpStateMachine.start();
Assert.assertThat(mAvrcpStateMachine.getCurrentState(),
IsInstanceOf.instanceOf(AvrcpControllerStateMachine.Disconnected.class));
mAvrcpStateMachine.connect(StackEvent.connectionStateChanged(control, browsing));
verify(mAvrcpControllerService, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcast(
mIntentArgument.capture(), eq(ProfileService.BLUETOOTH_PERM));
Assert.assertThat(mAvrcpStateMachine.getCurrentState(),
IsInstanceOf.instanceOf(AvrcpControllerStateMachine.Connected.class));
Assert.assertEquals(mAvrcpStateMachine.getState(), BluetoothProfile.STATE_CONNECTED);
return BluetoothProfile.STATE_CONNECTED;
}
}