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

Commit e7f8a673 authored by Sal Savage's avatar Sal Savage Committed by Gerrit Code Review
Browse files

Merge changes I4b929fed,I0221ab61,If8d6a4e6,I9fa90826,I8eba8517

* changes:
  Keep player set up to date, assign addressed player from it
  Create an AvrcpPlayer.Builder to consolidate constructors
  Add additional debugging output for available and addressed player status
  Fetch Available Players on our own when they change
  Ensure constructor's play status is included in PlaybackStateCompat
parents 685d3d77 46468069
Loading
Loading
Loading
Loading
+35 −24
Original line number Diff line number Diff line
@@ -222,6 +222,30 @@ public class AvrcpControllerService extends ProfileService {
        return false;
    }

    private int toPlaybackStateFromJni(int fromJni) {
        int playbackState = PlaybackStateCompat.STATE_NONE;
        switch (fromJni) {
            case JNI_PLAY_STATUS_STOPPED:
                playbackState = PlaybackStateCompat.STATE_STOPPED;
                break;
            case JNI_PLAY_STATUS_PLAYING:
                playbackState = PlaybackStateCompat.STATE_PLAYING;
                break;
            case JNI_PLAY_STATUS_PAUSED:
                playbackState = PlaybackStateCompat.STATE_PAUSED;
                break;
            case JNI_PLAY_STATUS_FWD_SEEK:
                playbackState = PlaybackStateCompat.STATE_FAST_FORWARDING;
                break;
            case JNI_PLAY_STATUS_REV_SEEK:
                playbackState = PlaybackStateCompat.STATE_REWINDING;
                break;
            default:
                playbackState = PlaybackStateCompat.STATE_NONE;
        }
        return playbackState;
    }

    protected AvrcpControllerStateMachine newStateMachine(BluetoothDevice device) {
        return new AvrcpControllerStateMachine(device, this);
    }
@@ -530,31 +554,12 @@ public class AvrcpControllerService extends ProfileService {
        if (DBG) {
            Log.d(TAG, "onPlayStatusChanged " + playStatus);
        }
        int playbackState = PlaybackStateCompat.STATE_NONE;
        switch (playStatus) {
            case JNI_PLAY_STATUS_STOPPED:
                playbackState = PlaybackStateCompat.STATE_STOPPED;
                break;
            case JNI_PLAY_STATUS_PLAYING:
                playbackState = PlaybackStateCompat.STATE_PLAYING;
                break;
            case JNI_PLAY_STATUS_PAUSED:
                playbackState = PlaybackStateCompat.STATE_PAUSED;
                break;
            case JNI_PLAY_STATUS_FWD_SEEK:
                playbackState = PlaybackStateCompat.STATE_FAST_FORWARDING;
                break;
            case JNI_PLAY_STATUS_REV_SEEK:
                playbackState = PlaybackStateCompat.STATE_REWINDING;
                break;
            default:
                playbackState = PlaybackStateCompat.STATE_NONE;
        }
        BluetoothDevice device = mAdapter.getRemoteDevice(address);
        AvrcpControllerStateMachine stateMachine = getStateMachine(device);
        if (stateMachine != null) {
            stateMachine.sendMessage(
                    AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_STATUS_CHANGED, playbackState);
                    AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_STATUS_CHANGED,
                    toPlaybackStateFromJni(playStatus));
        }
    }

@@ -698,10 +703,16 @@ public class AvrcpControllerService extends ProfileService {
                            + transportFlags + " play status " + playStatus + " player type "
                            + playerType);
        }

        BluetoothDevice device = mAdapter.getRemoteDevice(address);
        AvrcpPlayer player = new AvrcpPlayer(device, id, name, transportFlags, playStatus,
                playerType);
        return player;
        AvrcpPlayer.Builder apb = new AvrcpPlayer.Builder();
        apb.setDevice(device);
        apb.setPlayerId(id);
        apb.setPlayerType(playerType);
        apb.setSupportedFeatures(transportFlags);
        apb.setName(name);
        apb.setPlayStatus(toPlaybackStateFromJni(playStatus));
        return apb.build();
    }

    private void handleChangeFolderRsp(byte[] address, int count) {
+127 −18
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import android.media.AudioManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Message;
import android.support.v4.media.MediaBrowserCompat.MediaItem;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import android.util.Log;
@@ -125,9 +126,11 @@ class AvrcpControllerStateMachine extends StateMachine {
    boolean mRemoteControlConnected = false;
    boolean mBrowsingConnected = false;
    final BrowseTree mBrowseTree;
    private AvrcpPlayer mAddressedPlayer = new AvrcpPlayer();
    private int mAddressedPlayerId = -1;
    private SparseArray<AvrcpPlayer> mAvailablePlayerList = new SparseArray<AvrcpPlayer>();

    private AvrcpPlayer mAddressedPlayer;
    private int mAddressedPlayerId;
    private SparseArray<AvrcpPlayer> mAvailablePlayerList;

    private int mVolumeChangedNotificationsToIgnore = 0;
    private int mVolumeNotificationLabel = -1;

@@ -147,6 +150,20 @@ class AvrcpControllerStateMachine extends StateMachine {
        mCoverArtManager = service.getCoverArtManager();
        logD(device.toString());

        mAvailablePlayerList = new SparseArray<AvrcpPlayer>();
        mAddressedPlayerId = AvrcpPlayer.DEFAULT_ID;

        AvrcpPlayer.Builder apb = new AvrcpPlayer.Builder();
        apb.setDevice(mDevice);
        apb.setPlayerId(mAddressedPlayerId);
        apb.setSupportedFeature(AvrcpPlayer.FEATURE_PLAY);
        apb.setSupportedFeature(AvrcpPlayer.FEATURE_PAUSE);
        apb.setSupportedFeature(AvrcpPlayer.FEATURE_STOP);
        apb.setSupportedFeature(AvrcpPlayer.FEATURE_FORWARD);
        apb.setSupportedFeature(AvrcpPlayer.FEATURE_PREVIOUS);
        mAddressedPlayer = apb.build();
        mAvailablePlayerList.put(mAddressedPlayerId, mAddressedPlayer);

        mBrowseTree = new BrowseTree(mDevice);
        mDisconnected = new Disconnected();
        mConnecting = new Connecting();
@@ -215,6 +232,16 @@ class AvrcpControllerStateMachine extends StateMachine {
        return mAddressedPlayer.getCurrentTrack();
    }

    @VisibleForTesting
    int getAddressedPlayerId() {
        return mAddressedPlayerId;
    }

    @VisibleForTesting
    SparseArray<AvrcpPlayer> getAvailablePlayers() {
        return mAvailablePlayerList;
    }

    /**
     * Dump the current State Machine to the string builder.
     *
@@ -226,6 +253,22 @@ class AvrcpControllerStateMachine extends StateMachine {
        ProfileService.println(sb, "isActive: " + isActive());
        ProfileService.println(sb, "Control: " + mRemoteControlConnected);
        ProfileService.println(sb, "Browsing: " + mBrowsingConnected);
        ProfileService.println(sb, "Cover Art: "
                + (mCoverArtManager.getState(mDevice) == BluetoothProfile.STATE_CONNECTED));

        ProfileService.println(sb, "Addressed Player ID: " + mAddressedPlayerId);
        ProfileService.println(sb, "Available Players (" + mAvailablePlayerList.size() + "): ");
        for (int i = 0; i < mAvailablePlayerList.size(); i++) {
            AvrcpPlayer player = mAvailablePlayerList.valueAt(i);
            boolean isAddressed = (player.getId() == mAddressedPlayerId);
            ProfileService.println(sb, "\t" + (isAddressed ? "(Addressed) " : "") + player);
        }

        List<MediaItem> queue = null;
        if (mBrowseTree.mNowPlayingNode != null) {
            queue = mBrowseTree.mNowPlayingNode.getContents();
        }
        ProfileService.println(sb, "Queue (" + (queue == null ? 0 : queue.size()) + "): " + queue);
    }

    @VisibleForTesting
@@ -253,6 +296,7 @@ class AvrcpControllerStateMachine extends StateMachine {

    synchronized void onBrowsingConnected() {
        mBrowsingConnected = true;
        requestContents(mBrowseTree.mRootNode);
    }

    synchronized void onBrowsingDisconnected() {
@@ -262,8 +306,10 @@ class AvrcpControllerStateMachine extends StateMachine {
        String previousTrackUuid = previousTrack != null ? previousTrack.getCoverArtUuid() : null;
        mAddressedPlayer.updateCurrentTrack(null);
        mBrowseTree.mNowPlayingNode.setCached(false);
        mBrowseTree.mRootNode.setCached(false);
        if (isActive()) {
            BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mNowPlayingNode);
            BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mRootNode);
        }
        removeUnusedArtwork(previousTrackUuid);
        removeUnusedArtworkFromBrowseTree();
@@ -531,8 +577,10 @@ class AvrcpControllerStateMachine extends StateMachine {
                    return true;

                case MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED:
                    int oldAddressedPlayerId = mAddressedPlayerId;
                    mAddressedPlayerId = msg.arg1;
                    logD("AddressedPlayer = " + mAddressedPlayerId);
                    logD("AddressedPlayer changed " + oldAddressedPlayerId + " -> "
                            + 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
@@ -541,22 +589,37 @@ class AvrcpControllerStateMachine extends StateMachine {
                    if (isActive()) {
                        BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mNowPlayingNode);
                    }
                    removeUnusedArtworkFromBrowseTree();

                    AvrcpPlayer updatedPlayer = mAvailablePlayerList.get(mAddressedPlayerId);
                    if (updatedPlayer != null) {
                        mAddressedPlayer = updatedPlayer;
                        // If the new player supports the now playing feature then fetch it
                    // For devices that support browsing, we *may* have an AvrcpPlayer with player
                    // metadata already. We could also be in the middle fetching it. If the player
                    // isn't there then we need to ensure that a default Addressed AvrcpPlayer is
                    // created to represent it. It can be updated if/when we do fetch the player.
                    if (!mAvailablePlayerList.contains(mAddressedPlayerId)) {
                        logD("Available player set does not contain the new Addressed Player");
                        AvrcpPlayer.Builder apb = new AvrcpPlayer.Builder();
                        apb.setDevice(mDevice);
                        apb.setPlayerId(mAddressedPlayerId);
                        apb.setSupportedFeature(AvrcpPlayer.FEATURE_PLAY);
                        apb.setSupportedFeature(AvrcpPlayer.FEATURE_PAUSE);
                        apb.setSupportedFeature(AvrcpPlayer.FEATURE_STOP);
                        apb.setSupportedFeature(AvrcpPlayer.FEATURE_FORWARD);
                        apb.setSupportedFeature(AvrcpPlayer.FEATURE_PREVIOUS);
                        mAvailablePlayerList.put(mAddressedPlayerId, apb.build());
                    }

                    // Set our new addressed player object from our set of available players that's
                    // guaranteed to have the addressed player now.
                    mAddressedPlayer = mAvailablePlayerList.get(mAddressedPlayerId);

                    // Fetch metadata including the now playing list if the new player supports the
                    // now playing feature
                    mService.getCurrentMetadataNative(Utils.getByteAddress(mDevice));
                    mService.getPlaybackStateNative(Utils.getByteAddress(mDevice));
                    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);
                    }
                    removeUnusedArtworkFromBrowseTree();
                    logD("AddressedPlayer = " + mAddressedPlayer);
                    return true;

                case MESSAGE_PROCESS_SUPPORTED_APPLICATION_SETTINGS:
@@ -688,6 +751,7 @@ class AvrcpControllerStateMachine extends StateMachine {
            mBrowseTree.mRootNode.setExpectedChildren(255);
            BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mRootNode);
            removeUnusedArtworkFromBrowseTree();
            requestContents(mBrowseTree.mRootNode);
        }
    }

@@ -789,13 +853,58 @@ class AvrcpControllerStateMachine extends StateMachine {
                    break;

                case MESSAGE_PROCESS_GET_PLAYER_ITEMS:
                    logD("Received new available player items");
                    BrowseTree.BrowseNode rootNode = mBrowseTree.mRootNode;

                    // The specification is not firm on what receiving available player changes
                    // means relative to the existing player IDs, the addressed player and any
                    // currently saved play status, track or now playing list metadata. We're going
                    // to assume nothing and act verbosely, as some devices are known to reuse
                    // Player IDs.
                    if (!rootNode.isCached()) {
                        List<AvrcpPlayer> playerList = (List<AvrcpPlayer>) msg.obj;

                        // Since players hold metadata, including cover art handles that point to
                        // stored images, be sure to save image UUIDs so we can see if we can
                        // remove them from storage after setting our new player object
                        ArrayList<String> coverArtUuids = new ArrayList<String>();
                        for (int i = 0; i < mAvailablePlayerList.size(); i++) {
                            AvrcpPlayer player = mAvailablePlayerList.valueAt(i);
                            AvrcpItem track = player.getCurrentTrack();
                            if (track != null && track.getCoverArtUuid() != null) {
                                coverArtUuids.add(track.getCoverArtUuid());
                            }
                        }

                        mAvailablePlayerList.clear();
                        for (AvrcpPlayer player : playerList) {
                            mAvailablePlayerList.put(player.getId(), player);
                        }

                        // If our new set of players contains our addressed player again then we
                        // will replace it and re-download metadata. If not, we'll re-use the old
                        // player to save the metadata queries.
                        if (!mAvailablePlayerList.contains(mAddressedPlayerId)) {
                            logD("Available player set doesn't contain the addressed player");
                            mAvailablePlayerList.put(mAddressedPlayerId, mAddressedPlayer);
                        } else {
                            logD("Update addressed player with new available player metadata");
                            mAddressedPlayer = mAvailablePlayerList.get(mAddressedPlayerId);
                            mService.getCurrentMetadataNative(Utils.getByteAddress(mDevice));
                            mService.getPlaybackStateNative(Utils.getByteAddress(mDevice));
                            mBrowseTree.mNowPlayingNode.setCached(false);
                            if (mAddressedPlayer.supportsFeature(AvrcpPlayer.FEATURE_NOW_PLAYING)) {
                                sendMessage(MESSAGE_GET_FOLDER_ITEMS, mBrowseTree.mNowPlayingNode);
                            }
                        }
                        logD("AddressedPlayer = " + mAddressedPlayer);

                        // Check old cover art UUIDs for deletion
                        for (String uuid : coverArtUuids) {
                            removeUnusedArtwork(uuid);
                        }

                        // Make sure our browse tree matches our received Available Player set only
                        rootNode.addChildren(playerList);
                        mBrowseTree.setCurrentBrowsedFolder(BrowseTree.ROOT);
                        rootNode.setExpectedChildren(playerList.size());
+3 −2
Original line number Diff line number Diff line
@@ -240,8 +240,9 @@ public class AvrcpItem {
    public String toString() {
        return "AvrcpItem{mUuid=" + mUuid + ", mUid=" + mUid + ", mItemType=" + mItemType
                + ", mType=" + mType + ", mDisplayableName=" + mDisplayableName
                + ", mTitle=" + mTitle + ", mPlayable=" + mPlayable + ", mBrowsable="
                + mBrowsable + ", mCoverArtHandle=" + getCoverArtHandle()
                + ", mTitle=" + mTitle + " mPlayingTime=" + mPlayingTime + " mTrack="
                + mTrackNumber + "/" + mTotalNumberOfTracks + ", mPlayable=" + mPlayable
                + ", mBrowsable=" + mBrowsable + ", mCoverArtHandle=" + getCoverArtHandle()
                + ", mImageUuid=" + mImageUuid + ", mImageUri" + mImageUri + "}";
    }

+155 −19
Original line number Diff line number Diff line
@@ -32,7 +32,17 @@ class AvrcpPlayer {
    private static final String TAG = "AvrcpPlayer";
    private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);

    public static final int INVALID_ID = -1;
    public static final int DEFAULT_ID = -1;

    public static final int TYPE_UNKNOWN = -1;
    public static final int TYPE_AUDIO = 0;
    public static final int TYPE_VIDEO = 1;
    public static final int TYPE_BROADCASTING_AUDIO = 2;
    public static final int TYPE_BROADCASTING_VIDEO = 3;

    public static final int SUB_TYPE_UNKNOWN = -1;
    public static final int SUB_TYPE_AUDIO_BOOK = 0;
    public static final int SUB_TYPE_PODCAST = 1;

    public static final int FEATURE_PLAY = 40;
    public static final int FEATURE_STOP = 41;
@@ -60,31 +70,18 @@ class AvrcpPlayer {
            new PlayerApplicationSettings();
    private PlayerApplicationSettings mCurrentPlayerApplicationSettings;

    AvrcpPlayer() {
        mDevice = null;
        mId = INVALID_ID;
        //Set Default Actions in case Player data isn't available.
        mAvailableActions = PlaybackStateCompat.ACTION_PAUSE | PlaybackStateCompat.ACTION_PLAY
                | PlaybackStateCompat.ACTION_SKIP_TO_NEXT
                | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
                | PlaybackStateCompat.ACTION_STOP | PlaybackStateCompat.ACTION_PREPARE;
        PlaybackStateCompat.Builder playbackStateBuilder = new PlaybackStateCompat.Builder()
                .setActions(mAvailableActions);
        mPlaybackStateCompat = playbackStateBuilder.build();
    }

    AvrcpPlayer(BluetoothDevice device, int id, String name, byte[] playerFeatures, int playStatus,
            int playerType) {
    private AvrcpPlayer(BluetoothDevice device, int id, int playerType, int playerSubType,
            String name, byte[] playerFeatures, int playStatus) {
        mDevice = device;
        mId = id;
        mName = name;
        mPlayStatus = playStatus;
        mPlayerType = playerType;
        mPlayerFeatures = Arrays.copyOf(playerFeatures, playerFeatures.length);
        PlaybackStateCompat.Builder playbackStateBuilder = new PlaybackStateCompat.Builder()
                .setActions(mAvailableActions);
        mPlaybackStateCompat = playbackStateBuilder.build();
        updateAvailableActions();
        setPlayStatus(playStatus);
    }

    public BluetoothDevice getDevice() {
@@ -112,8 +109,10 @@ class AvrcpPlayer {
    }

    public void setPlayStatus(int playStatus) {
        if (mPlayTime != PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN) {
            mPlayTime += mPlaySpeed * (SystemClock.elapsedRealtime()
                    - mPlaybackStateCompat.getLastPositionUpdateTime());
        }
        mPlayStatus = playStatus;
        switch (mPlayStatus) {
            case PlaybackStateCompat.STATE_STOPPED:
@@ -202,6 +201,7 @@ class AvrcpPlayer {
    }

    private void updateAvailableActions() {
        mAvailableActions = PlaybackStateCompat.ACTION_PREPARE;
        if (supportsFeature(FEATURE_PLAY)) {
            mAvailableActions = mAvailableActions | PlaybackStateCompat.ACTION_PLAY;
        }
@@ -236,4 +236,140 @@ class AvrcpPlayer {

        if (DBG) Log.d(TAG, "Supported Actions = " + mAvailableActions);
    }

    @Override
    public String toString() {
        return "<AvrcpPlayer id=" + mId + " name=" + mName + " track=" + mCurrentTrack
                + " playState=" + mPlaybackStateCompat + ">";
    }

    /**
     * A Builder object for an AvrcpPlayer
     */
    public static class Builder {
        private static final String TAG = "AvrcpPlayer.Builder";
        private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);

        private BluetoothDevice mDevice = null;
        private int mPlayerId = AvrcpPlayer.DEFAULT_ID;
        private int mPlayerType = AvrcpPlayer.TYPE_UNKNOWN;
        private int mPlayerSubType = AvrcpPlayer.SUB_TYPE_UNKNOWN;
        private String mPlayerName = null;
        private byte[] mSupportedFeatures = new byte[16];

        private int mPlayStatus = PlaybackStateCompat.STATE_NONE;
        private long mPlayTime = PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN;
        private float mPlaySpeed = 1;
        private long mPlayTimeUpdate = 0;

        private AvrcpItem mTrack = null;

        /**
         * Set the device that this Player came from
         *
         * @param device The BleutoothDevice representing the remote device
         * @return This object, so you can continue building
         */
        public Builder setDevice(BluetoothDevice device) {
            mDevice = device;
            return this;
        }

        /**
         * Set the Player ID for this Player
         *
         * @param playerId The ID for this player, defined in AVRCP 6.10.2.1
         * @return This object, so you can continue building
         */
        public Builder setPlayerId(int playerId) {
            mPlayerId = playerId;
            return this;
        }

        /**
         * Set the Player Type for this Player
         *
         * @param playerType The type for this player, defined in AVRCP 6.10.2.1
         * @return This object, so you can continue building
         */
        public Builder setPlayerType(int playerType) {
            mPlayerType = playerType;
            return this;
        }

        /**
         * Set the Player Sub-type for this Player
         *
         * @param playerSubType The sub-type for this player, defined in AVRCP 6.10.2.1
         * @return This object, so you can continue building
         */
        public Builder setPlayerSubType(int playerSubType) {
            mPlayerSubType = playerSubType;
            return this;
        }

        /**
         * Set the name for this Player. This is what users will see when browsing.
         *
         * @param name The name for this player, defined in AVRCP 6.10.2.1
         * @return This object, so you can continue building
         */
        public Builder setName(String name) {
            mPlayerName = name;
            return this;
        }

        /**
         * Set the entire set of supported features for this Player.
         *
         * @param features The feature set for this player, defined in AVRCP 6.10.2.1
         * @return This object, so you can continue building
         */
        public Builder setSupportedFeatures(byte[] supportedFeatures) {
            mSupportedFeatures = supportedFeatures;
            return this;
        }

        /**
         * Set a single features as supported for this Player.
         *
         * @param feature The feature for this player, defined in AVRCP 6.10.2.1
         * @return This object, so you can continue building
         */
        public Builder setSupportedFeature(int feature) {
            int byteNumber = feature / 8;
            byte bitMask = (byte) (1 << (feature % 8));
            mSupportedFeatures[byteNumber] = (byte) (mSupportedFeatures[byteNumber] | bitMask);
            return this;
        }

        /**
         * Set the initial play status of the Player.
         *
         * @param playStatus The play state for this player as a PlaybackStateCompat.STATE_* value
         * @return This object, so you can continue building
         */
        public Builder setPlayStatus(int playStatus) {
            mPlayStatus = playStatus;
            return this;
        }

        /**
         * Set the initial play status of the Player.
         *
         * @param track The initial track for this player
         * @return This object, so you can continue building
         */
        public Builder setCurrentTrack(AvrcpItem track) {
            mTrack = track;
            return this;
        }

        public AvrcpPlayer build() {
            AvrcpPlayer player = new AvrcpPlayer(mDevice, mPlayerId, mPlayerType, mPlayerSubType,
                    mPlayerName, mSupportedFeatures, mPlayStatus);
            player.updateCurrentTrack(mTrack);
            return player;
        }
    }
}
+240 −22

File changed.

Preview size limit exceeded, changes collapsed.