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

Commit 3713e8af authored by Etienne Ruffieux's avatar Etienne Ruffieux
Browse files

Implement AVRCP's set addressed player

This implements SetAddressedPlayer properly now that we
have an utility for it (since the browsing refactor).

Android's MediaSessions doesn't allow to set an active
session from its controller, hence we can't set the
session as active with SetAddressedPlayer and we need to
keep the "active", "browsed" and add an "addressed" session
id.
The "active" session corresponds to the session registered
to Android media key events - the session Android Media
considers as active.
The "browsed" session is the one the remote is currently
browsing. Can be any players and isn't linked to the
"active" session.

The "addressed" session is the session the remote whishes
to control. Once a play event is received form the CT, we
check if there is an "addressed" session and proceed to
send the play event to it instead of just sending it to the
active session. This will in turn modify the "active"
session to become the addressed one.
If the active session is changed directly on TG, then the
"addressed" session will change to it and TG will notify CT

As of now, controls other than PLAY will still be sent to
the "active" session. For two reasons:
- We don't have a way to make the "addressed" session
active in Android Media, so the previous session will
continue to play and we want to be able to control it.
This fits the section 29.15 from AVRCP 1.6.2 spec.
- Sending MEDIA_NEXT to a non active session wouldn't do
anything.

Bug: 346717077
Flag: com.android.bluetooth.flags.set_addressed_player
Test: atest pts-bot:AVRCP
Change-Id: I6dae8ead88195abd7a73230793e1df8d02501d6f
parent 551dc422
Loading
Loading
Loading
Loading
+25 −0
Original line number Diff line number Diff line
@@ -62,6 +62,7 @@ static uint16_t getCurrentPlayerId();
static std::vector<MediaPlayerInfo> getMediaPlayerList();
using SetBrowsedPlayerCb = MediaInterface::SetBrowsedPlayerCallback;
static void setBrowsedPlayer(uint16_t player_id, SetBrowsedPlayerCb);
static uint16_t setAddressedPlayer(uint16_t player_id);
using GetFolderItemsCb = MediaInterface::FolderItemsCallback;
static void getFolderItems(uint16_t player_id, std::string media_id, GetFolderItemsCb cb);
static void playItem(uint16_t player_id, bool now_playing, std::string media_id);
@@ -146,10 +147,19 @@ public:
    getFolderItems(player_id, media_id, folder_cb);
  }

  void GetAddressedPlayer(GetAddressedPlayerCallback cb) override {
    uint16_t current_player = getCurrentPlayerId();
    cb.Run(current_player);
  }

  void SetBrowsedPlayer(uint16_t player_id, SetBrowsedPlayerCallback browse_cb) override {
    setBrowsedPlayer(player_id, browse_cb);
  }

  void SetAddressedPlayer(uint16_t player_id, SetAddressedPlayerCallback addressed_cb) override {
    addressed_cb.Run(setAddressedPlayer(player_id));
  }

  void RegisterUpdateCallback(MediaCallbacks* callback) override {
    // TODO(apanicke): Allow multiple registrations in the future
    mServiceCallbacks = callback;
@@ -209,6 +219,7 @@ static jmethodID method_getCurrentMediaId;
static jmethodID method_getNowPlayingList;

static jmethodID method_setBrowsedPlayer;
static jmethodID method_setAddressedPlayer;
static jmethodID method_getCurrentPlayerId;
static jmethodID method_getMediaPlayerList;
static jmethodID method_getFolderItemsRequest;
@@ -695,6 +706,19 @@ static void setBrowsedPlayerResponseNative(JNIEnv* env, jobject /* object */, ji
  set_browsed_player_cb.Run(success == JNI_TRUE, root, num_items);
}

static uint16_t setAddressedPlayer(uint16_t player_id) {
  log::debug("");
  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
  CallbackEnv sCallbackEnv(__func__);
  if (!sCallbackEnv.valid() || !mJavaInterface) {
    return 0u;
  }

  jint new_player =
          sCallbackEnv->CallIntMethod(mJavaInterface, method_setAddressedPlayer, player_id);
  return static_cast<int>(new_player) & 0xFFFF;
}

static void getFolderItemsResponseNative(JNIEnv* env, jobject /* object */, jstring parent_id,
                                         jobject list) {
  log::debug("");
@@ -1065,6 +1089,7 @@ int register_com_android_bluetooth_avrcp_target(JNIEnv* env) {
          {"getCurrentPlayerId", "()I", &method_getCurrentPlayerId},
          {"getMediaPlayerList", "()Ljava/util/List;", &method_getMediaPlayerList},
          {"setBrowsedPlayer", "(I)V", &method_setBrowsedPlayer},
          {"setAddressedPlayer", "(I)I", &method_setAddressedPlayer},
          {"getFolderItemsRequest", "(ILjava/lang/String;)V", &method_getFolderItemsRequest},
          {"playItem", "(IZLjava/lang/String;)V", &method_playItem},
          {"setActiveDevice", "(Ljava/lang/String;)V", &method_setActiveDevice},
+59 −16
Original line number Diff line number Diff line
@@ -102,6 +102,7 @@ public class MediaPlayerList {
            Collections.synchronizedMap(new HashMap<Integer, MediaBrowserWrapper>());
    private int mActivePlayerId = NO_ACTIVE_PLAYER;
    private int mBrowsingPlayerId = NO_ACTIVE_PLAYER;
    private int mAddressedPlayerId = NO_ACTIVE_PLAYER;

    private MediaUpdateCallback mCallback;
    private boolean mAudioPlaybackIsActive = false;
@@ -329,7 +330,9 @@ public class MediaPlayerList {

    /** returns the current player ID. */
    public int getCurrentPlayerId() {
        if (Flags.browsingRefactor()) {
        if (Flags.setAddressedPlayer()) {
            return mAddressedPlayerId;
        } else if (Flags.browsingRefactor()) {
            return mBrowsingPlayerId;
        } else {
            return BLUETOOTH_PLAYER_ID;
@@ -350,7 +353,16 @@ public class MediaPlayerList {
        return mMediaPlayers.get(mActivePlayerId);
    }

    /** This is used to send passthrough command to media session */
    /** Returns the {@link #MediaPlayerWrapper} with ID matching {@link #mAddressedPlayerId}. */
    public MediaPlayerWrapper getAddressedPlayer() {
        return mMediaPlayers.get(mAddressedPlayerId);
    }

    /**
     * This is used to send passthrough command to media session
     *
     * <p>Note: This is used only by MCP, AVRCP uses AvrcpTargetService.
     */
    public void sendMediaKeyEvent(int key, boolean pushed) {
        if (mMediaSessionManager == null) {
            Log.d(TAG, "Bluetooth is turning off, ignore it");
@@ -363,8 +375,11 @@ public class MediaPlayerList {

    public void getPlayerRoot(int playerId, GetPlayerRootCallback cb) {
        if (Flags.browsingRefactor()) {
            if (!haveMediaBrowser(playerId)) {
                cb.run(playerId, false, "", 0);
                return;
            }
            mBrowsingPlayerId = playerId;
            if (haveMediaBrowser(playerId)) {
            MediaBrowserWrapper wrapper = mMediaBrowserWrappers.get(playerId);
            wrapper.getRootId(
                    (rootId) -> {
@@ -374,8 +389,6 @@ public class MediaPlayerList {
                                    cb.run(playerId, true, rootId, itemList.size());
                                });
                    });
                sendFolderUpdate(false, true, false);
            }
        } else {
            // Fix PTS AVRCP/TG/MCN/CB/BI-02-C
            if (Utils.isPtsTestMode()) {
@@ -398,6 +411,18 @@ public class MediaPlayerList {
        }
    }

    /** Sets which player the AV/C commands should be addressed to. */
    public int setAddressedPlayer(int playerId) {
        if (!Flags.setAddressedPlayer()) {
            return BLUETOOTH_PLAYER_ID;
        }
        if (mMediaPlayerIds.containsValue(playerId)) {
            mAddressedPlayerId = playerId;
            sendFolderUpdate(false, true, false);
        }
        return mAddressedPlayerId;
    }

    /** Returns a list valid browsable players. */
    public List<PlayerInfo> getMediaPlayerList() {
        List<PlayerInfo> ret = new ArrayList<PlayerInfo>();
@@ -564,8 +589,15 @@ public class MediaPlayerList {
        }

        long queueItemId = Long.parseLong(m.group(1));
        if (getActivePlayer() != null) {
            getActivePlayer().playItemFromQueue(queueItemId);

        MediaPlayerWrapper player;
        if (Flags.setAddressedPlayer()) {
            player = getAddressedPlayer();
        } else {
            player = getActivePlayer();
        }
        if (player != null) {
            player.playItemFromQueue(queueItemId);
        }
    }

@@ -894,7 +926,11 @@ public class MediaPlayerList {
        // tells us otherwise
        if (playerId == mActivePlayerId && playerId != NO_ACTIVE_PLAYER) {
            getActivePlayer().unregisterCallback();
            if (mAddressedPlayerId == mActivePlayerId) {
                mAddressedPlayerId = NO_ACTIVE_PLAYER;
            }
            mActivePlayerId = NO_ACTIVE_PLAYER;

            List<Metadata> queue = new ArrayList<Metadata>();
            queue.add(Util.empty_data());
            MediaData newData = new MediaData(Util.empty_data(), null, queue);
@@ -948,6 +984,13 @@ public class MediaPlayerList {

        if (Utils.isPtsTestMode()) {
            sendFolderUpdate(true, true, false);
        } else if (Flags.setAddressedPlayer()) {
            // If the new active player has been set by Addressed player key event
            // We don't send an addressed player update.
            if (mActivePlayerId != mAddressedPlayerId) {
                mAddressedPlayerId = mActivePlayerId;
                sendFolderUpdate(false, true, false);
            }
        }

        MediaData data = getActivePlayer().getCurrentMediaData();
+5 −0
Original line number Diff line number Diff line
@@ -192,6 +192,11 @@ public class AvrcpNativeInterface {
        setBrowsedPlayerResponseNative(playerId, success, rootId, numItems);
    }

    int setAddressedPlayer(int playerId) {
        d("setAddressedPlayer: playerId=" + playerId);
        return mAvrcpService.setAddressedPlayer(playerId);
    }

    void getFolderItemsRequest(int playerId, String mediaId) {
        d("getFolderItemsRequest: playerId=" + playerId + " mediaId=" + mediaId);
        mAvrcpService.getFolderItems(playerId, mediaId, (a, b) -> getFolderItemsResponse(a, b));
+22 −2
Original line number Diff line number Diff line
@@ -47,6 +47,7 @@ import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.MetricsLogger;
import com.android.bluetooth.btservice.ProfileService;
import com.android.bluetooth.btservice.ServiceFactory;
import com.android.bluetooth.flags.Flags;
import com.android.internal.annotations.VisibleForTesting;

import java.util.List;
@@ -485,6 +486,11 @@ public class AvrcpTargetService extends ProfileService {
        mMediaPlayerList.getPlayerRoot(playerId, cb);
    }

    /** See {@link MediaPlayerList#setAddressedPlayer}. */
    int setAddressedPlayer(int playerId) {
        return mMediaPlayerList.setAddressedPlayer(playerId);
    }

    /** See {@link MediaPlayerList#getFolderItems}. */
    void getFolderItems(int playerId, String mediaId, MediaPlayerList.GetFolderItemsCallback cb) {
        mMediaPlayerList.getFolderItems(playerId, mediaId, cb);
@@ -499,8 +505,22 @@ public class AvrcpTargetService extends ProfileService {

    /** Informs {@link AudioManager} of an incoming key event from a remote device. */
    void sendMediaKeyEvent(int key, boolean pushed) {
        MediaPlayerWrapper activePlayer = mMediaPlayerList.getActivePlayer();
        if (Flags.setAddressedPlayer()) {
            MediaPlayerWrapper addressedPlayer = mMediaPlayerList.getAddressedPlayer();
            // A/V controls should be sent to the addressed player.
            // We don't have a way to set a media player as the active session so we
            // keep the active device playing until we receive a PLAY event for the
            // addressed player. Other events will still be broadcasted to active player.
            if (addressedPlayer != null
                    && KeyEvent.KEYCODE_MEDIA_PLAY == AvrcpPassthrough.toKeyCode(key)
                    && activePlayer != addressedPlayer) {
                addressedPlayer.playCurrent();
                return;
            }
        }

        BluetoothDevice activeDevice = getA2dpActiveDevice();
        MediaPlayerWrapper player = mMediaPlayerList.getActivePlayer();
        mMediaKeyEventLogger.logd(
                TAG,
                "sendMediaKeyEvent:"
@@ -511,7 +531,7 @@ public class AvrcpTargetService extends ProfileService {
                        + " pushed="
                        + pushed
                        + " to "
                        + (player == null ? null : player.getPackageName()));
                        + (activePlayer == null ? null : activePlayer.getPackageName()));
        int action = pushed ? KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP;
        KeyEvent event = new KeyEvent(action, AvrcpPassthrough.toKeyCode(key));
        mAudioManager.dispatchMediaKeyEvent(event);
+1 −1
Original line number Diff line number Diff line
@@ -240,7 +240,7 @@ class MediaPlayerBrowserService : MediaBrowserServiceCompat() {
            result.sendResult(mediaIdToChildren[NOW_PLAYING_PREFIX])
        } else {
            Log.i(TAG, "onloadchildren inside else")
            result.sendResult(null)
            result.sendResult(mediaIdToChildren[ROOT])
        }
    }

Loading