Loading android/app/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java +14 −0 Original line number Diff line number Diff line Loading @@ -460,11 +460,25 @@ class AvrcpControllerStateMachine extends StateMachine { case MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED: mAddressedPlayerId = msg.arg1; logD("AddressedPlayer = " + mAddressedPlayerId); // The now playing list is tied to the addressed player by specification in // AVRCP 5.9.1. A new addressed player means our now playing content is now // invalid mBrowseTree.mNowPlayingNode.setCached(false); if (isActive()) { BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mNowPlayingNode); } AvrcpPlayer updatedPlayer = mAvailablePlayerList.get(mAddressedPlayerId); if (updatedPlayer != null) { mAddressedPlayer = updatedPlayer; // If the new player supports the now playing feature then fetch it if (mAddressedPlayer.supportsFeature(AvrcpPlayer.FEATURE_NOW_PLAYING)) { sendMessage(MESSAGE_GET_FOLDER_ITEMS, mBrowseTree.mNowPlayingNode); } logD("AddressedPlayer = " + mAddressedPlayer.getName()); } else { logD("Addressed player changed to unknown ID=" + mAddressedPlayerId); mBrowseTree.mRootNode.setCached(false); mBrowseTree.mRootNode.setExpectedChildren(255); BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mRootNode); Loading android/app/src/com/android/bluetooth/avrcpcontroller/AvrcpItem.java +48 −0 Original line number Diff line number Diff line Loading @@ -24,6 +24,8 @@ import android.support.v4.media.MediaDescriptionCompat; import android.support.v4.media.MediaMetadataCompat; import android.util.Log; import java.util.Objects; /** * An object representing a single item returned from an AVRCP folder listing in the VFS scope. * Loading Loading @@ -105,6 +107,14 @@ public class AvrcpItem { return mUuid; } public int getItemType() { return mItemType; } public int getType() { return mType; } public String getDisplayableName() { return mDisplayableName; } Loading @@ -129,6 +139,14 @@ public class AvrcpItem { return mTotalNumberOfTracks; } public String getGenre() { return mGenre; } public long getPlayingTime() { return mPlayingTime; } public boolean isPlayable() { return mPlayable; } Loading Loading @@ -211,6 +229,36 @@ public class AvrcpItem { + mBrowsable + ", mCoverArtHandle=" + getCoverArtHandle() + "}"; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof AvrcpItem)) { return false; } AvrcpItem other = ((AvrcpItem) o); return Objects.equals(mUuid, other.getUuid()) && Objects.equals(mDevice, other.getDevice()) && Objects.equals(mUid, other.getUid()) && Objects.equals(mItemType, other.getItemType()) && Objects.equals(mType, other.getType()) && Objects.equals(mTitle, other.getTitle()) && Objects.equals(mDisplayableName, other.getDisplayableName()) && Objects.equals(mArtistName, other.getArtistName()) && Objects.equals(mAlbumName, other.getAlbumName()) && Objects.equals(mTrackNumber, other.getTrackNumber()) && Objects.equals(mTotalNumberOfTracks, other.getTotalNumberOfTracks()) && Objects.equals(mGenre, other.getGenre()) && Objects.equals(mPlayingTime, other.getPlayingTime()) && Objects.equals(mCoverArtHandle, other.getCoverArtHandle()) && Objects.equals(mPlayable, other.isPlayable()) && Objects.equals(mBrowsable, other.isBrowsable()) && Objects.equals(mImageUri, other.getCoverArtLocation()); } /** * Builder for an AvrcpItem */ Loading android/app/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java +1 −0 Original line number Diff line number Diff line Loading @@ -42,6 +42,7 @@ class AvrcpPlayer { public static final int FEATURE_FORWARD = 47; public static final int FEATURE_PREVIOUS = 48; public static final int FEATURE_BROWSING = 59; public static final int FEATURE_NOW_PLAYING = 65; private BluetoothDevice mDevice; private int mPlayStatus = PlaybackStateCompat.STATE_NONE; Loading android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java +126 −9 Original line number Diff line number Diff line Loading @@ -55,6 +55,7 @@ import org.mockito.MockitoAnnotations; import java.util.ArrayList; import java.util.List; import java.util.UUID; @MediumTest @RunWith(AndroidJUnit4.class) Loading Loading @@ -517,15 +518,71 @@ public class AvrcpControllerStateMachineTest { } /** * Test addressed media player changed * 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 * Verify that the contents of a player are fetched upon request */ @Test public void testPlayerChanged() { setUpConnectedState(true, true); final String rootName = "__ROOT__"; final String playerName = "Player 1"; //Get the root of the device BrowseTree.BrowseNode results = mAvrcpStateMachine.findNode(rootName); Loading @@ -538,25 +595,85 @@ public class AvrcpControllerStateMachineTest { timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).getPlayerListNative(eq(mTestAddress), eq(0), eq(19)); //Provide back a player object //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, playerName, playerFeatures, 1, 1); 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, 4); results = mAvrcpStateMachine.findNode(rootName); AvrcpControllerStateMachine.MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED, 2); TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper()); mAvrcpStateMachine.requestContents(results); //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(2)).getPlayerListNative(eq(mTestAddress), 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()); } /** Loading Loading
android/app/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java +14 −0 Original line number Diff line number Diff line Loading @@ -460,11 +460,25 @@ class AvrcpControllerStateMachine extends StateMachine { case MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED: mAddressedPlayerId = msg.arg1; logD("AddressedPlayer = " + mAddressedPlayerId); // The now playing list is tied to the addressed player by specification in // AVRCP 5.9.1. A new addressed player means our now playing content is now // invalid mBrowseTree.mNowPlayingNode.setCached(false); if (isActive()) { BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mNowPlayingNode); } AvrcpPlayer updatedPlayer = mAvailablePlayerList.get(mAddressedPlayerId); if (updatedPlayer != null) { mAddressedPlayer = updatedPlayer; // If the new player supports the now playing feature then fetch it if (mAddressedPlayer.supportsFeature(AvrcpPlayer.FEATURE_NOW_PLAYING)) { sendMessage(MESSAGE_GET_FOLDER_ITEMS, mBrowseTree.mNowPlayingNode); } logD("AddressedPlayer = " + mAddressedPlayer.getName()); } else { logD("Addressed player changed to unknown ID=" + mAddressedPlayerId); mBrowseTree.mRootNode.setCached(false); mBrowseTree.mRootNode.setExpectedChildren(255); BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mRootNode); Loading
android/app/src/com/android/bluetooth/avrcpcontroller/AvrcpItem.java +48 −0 Original line number Diff line number Diff line Loading @@ -24,6 +24,8 @@ import android.support.v4.media.MediaDescriptionCompat; import android.support.v4.media.MediaMetadataCompat; import android.util.Log; import java.util.Objects; /** * An object representing a single item returned from an AVRCP folder listing in the VFS scope. * Loading Loading @@ -105,6 +107,14 @@ public class AvrcpItem { return mUuid; } public int getItemType() { return mItemType; } public int getType() { return mType; } public String getDisplayableName() { return mDisplayableName; } Loading @@ -129,6 +139,14 @@ public class AvrcpItem { return mTotalNumberOfTracks; } public String getGenre() { return mGenre; } public long getPlayingTime() { return mPlayingTime; } public boolean isPlayable() { return mPlayable; } Loading Loading @@ -211,6 +229,36 @@ public class AvrcpItem { + mBrowsable + ", mCoverArtHandle=" + getCoverArtHandle() + "}"; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof AvrcpItem)) { return false; } AvrcpItem other = ((AvrcpItem) o); return Objects.equals(mUuid, other.getUuid()) && Objects.equals(mDevice, other.getDevice()) && Objects.equals(mUid, other.getUid()) && Objects.equals(mItemType, other.getItemType()) && Objects.equals(mType, other.getType()) && Objects.equals(mTitle, other.getTitle()) && Objects.equals(mDisplayableName, other.getDisplayableName()) && Objects.equals(mArtistName, other.getArtistName()) && Objects.equals(mAlbumName, other.getAlbumName()) && Objects.equals(mTrackNumber, other.getTrackNumber()) && Objects.equals(mTotalNumberOfTracks, other.getTotalNumberOfTracks()) && Objects.equals(mGenre, other.getGenre()) && Objects.equals(mPlayingTime, other.getPlayingTime()) && Objects.equals(mCoverArtHandle, other.getCoverArtHandle()) && Objects.equals(mPlayable, other.isPlayable()) && Objects.equals(mBrowsable, other.isBrowsable()) && Objects.equals(mImageUri, other.getCoverArtLocation()); } /** * Builder for an AvrcpItem */ Loading
android/app/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java +1 −0 Original line number Diff line number Diff line Loading @@ -42,6 +42,7 @@ class AvrcpPlayer { public static final int FEATURE_FORWARD = 47; public static final int FEATURE_PREVIOUS = 48; public static final int FEATURE_BROWSING = 59; public static final int FEATURE_NOW_PLAYING = 65; private BluetoothDevice mDevice; private int mPlayStatus = PlaybackStateCompat.STATE_NONE; Loading
android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java +126 −9 Original line number Diff line number Diff line Loading @@ -55,6 +55,7 @@ import org.mockito.MockitoAnnotations; import java.util.ArrayList; import java.util.List; import java.util.UUID; @MediumTest @RunWith(AndroidJUnit4.class) Loading Loading @@ -517,15 +518,71 @@ public class AvrcpControllerStateMachineTest { } /** * Test addressed media player changed * 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 * Verify that the contents of a player are fetched upon request */ @Test public void testPlayerChanged() { setUpConnectedState(true, true); final String rootName = "__ROOT__"; final String playerName = "Player 1"; //Get the root of the device BrowseTree.BrowseNode results = mAvrcpStateMachine.findNode(rootName); Loading @@ -538,25 +595,85 @@ public class AvrcpControllerStateMachineTest { timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).getPlayerListNative(eq(mTestAddress), eq(0), eq(19)); //Provide back a player object //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, playerName, playerFeatures, 1, 1); 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, 4); results = mAvrcpStateMachine.findNode(rootName); AvrcpControllerStateMachine.MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED, 2); TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper()); mAvrcpStateMachine.requestContents(results); //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(2)).getPlayerListNative(eq(mTestAddress), 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()); } /** Loading