Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c3ea15a2 authored by Sal Savage's avatar Sal Savage
Browse files

Clear now playing list on player change, update if player supports it

The now playing list is always expected to be tied to the addressed
player by specification. This means if we change to a new addressed
player then any now playing list we have is no long valid. This change
will invalidate the now playing list on notification of a player change
and query the new players features to see if we should re-request the
list.

This fixes bugs where player changes would leave around the previous
players list, especially if the new player didn't support a now playing
list.

This change also adds tests for the state machine and some backing code
that other tests can use to get, set, and assert the value of the now
playing list.

Bug: 148701801
Test: build, flash, test with target devices, atest
Change-Id: I4c36f2917a8844863384a3cb2fca02a3837cc581
parent d4220355
Loading
Loading
Loading
Loading
+14 −0
Original line number Diff line number Diff line
@@ -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);
+48 −0
Original line number Diff line number Diff line
@@ -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.
 *
@@ -105,6 +107,14 @@ public class AvrcpItem {
        return mUuid;
    }

    public int getItemType() {
        return mItemType;
    }

    public int getType() {
        return mType;
    }

    public String getDisplayableName() {
        return mDisplayableName;
    }
@@ -129,6 +139,14 @@ public class AvrcpItem {
        return mTotalNumberOfTracks;
    }

    public String getGenre() {
        return mGenre;
    }

    public long getPlayingTime() {
        return mPlayingTime;
    }

    public boolean isPlayable() {
        return mPlayable;
    }
@@ -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
     */
+1 −0
Original line number Diff line number Diff line
@@ -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;
+126 −9
Original line number Diff line number Diff line
@@ -55,6 +55,7 @@ import org.mockito.MockitoAnnotations;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

@MediumTest
@RunWith(AndroidJUnit4.class)
@@ -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);
@@ -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());
    }

    /**