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

Commit 3cbb18c7 authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

Merge "Support for AVRCP Player Application settings"

parents d559f899 8fa49e54
Loading
Loading
Loading
Loading
+206 −1
Original line number Diff line number Diff line
@@ -62,6 +62,27 @@ static void volumeDeviceConnected(
static void volumeDeviceDisconnected(const RawAddress& address);
static void setVolume(int8_t volume);

using ListPlayerSettingsCb =
    PlayerSettingsInterface::ListPlayerSettingsCallback;
static void listPlayerSettings(ListPlayerSettingsCb cb);
ListPlayerSettingsCb list_player_settings_cb;
using ListPlayerSettingValuesCb =
    PlayerSettingsInterface::ListPlayerSettingValuesCallback;
static void listPlayerSettingValues(PlayerAttribute setting,
                                    ListPlayerSettingValuesCb cb);
ListPlayerSettingValuesCb list_player_setting_values_cb;
using GetCurrentPlayerSettingValueCb =
    PlayerSettingsInterface::GetCurrentPlayerSettingValueCallback;
static void getPlayerSettings(std::vector<PlayerAttribute> attributes,
                              GetCurrentPlayerSettingValueCb cb);
GetCurrentPlayerSettingValueCb get_current_player_setting_value_cb;
using SetPlayerSettingValueCb =
    PlayerSettingsInterface::SetPlayerSettingValueCallback;
static void setPlayerSettings(std::vector<PlayerAttribute> attributes,
                              std::vector<uint8_t> values,
                              SetPlayerSettingValueCb cb);
SetPlayerSettingValueCb set_player_setting_value_cb;

// Local Variables
// TODO (apanicke): Use a map here to store the callback in order to
// support multi-browsing
@@ -71,6 +92,18 @@ std::map<std::string, GetFolderItemsCb> get_folder_items_cb_map;
std::map<RawAddress, ::bluetooth::avrcp::VolumeInterface::VolumeChangedCb>
    volumeCallbackMap;

template <typename T>
void copyJavaArraytoCppVector(JNIEnv* env, const jbyteArray& jArray,
                              std::vector<T>& cVec) {
  size_t len = (size_t)env->GetArrayLength(jArray);
  if (len == 0) return;
  jbyte* elements = env->GetByteArrayElements(jArray, nullptr);
  T* array = reinterpret_cast<T*>(elements);
  cVec.reserve(len);
  std::copy(array, array + len, std::back_inserter(cVec));
  env->ReleaseByteArrayElements(jArray, elements, 0);
}

// TODO (apanicke): In the future, this interface should guarantee that
// all calls happen on the JNI Thread. Right now this is very difficult
// as it is hard to get a handle on the JNI thread from here.
@@ -150,6 +183,30 @@ class VolumeInterfaceImpl : public VolumeInterface {
};
static VolumeInterfaceImpl mVolumeInterface;

class PlayerSettingsInterfaceImpl : public PlayerSettingsInterface {
 public:
  void ListPlayerSettings(ListPlayerSettingsCallback cb) {
    listPlayerSettings(cb);
  }

  void ListPlayerSettingValues(PlayerAttribute setting,
                               ListPlayerSettingValuesCallback cb) {
    listPlayerSettingValues(setting, cb);
  }

  void GetCurrentPlayerSettingValue(std::vector<PlayerAttribute> attributes,
                                    GetCurrentPlayerSettingValueCallback cb) {
    getPlayerSettings(attributes, cb);
  }

  void SetPlayerSettings(std::vector<PlayerAttribute> attributes,
                         std::vector<uint8_t> values,
                         SetPlayerSettingValueCallback cb) {
    setPlayerSettings(attributes, values, cb);
  }
};
static PlayerSettingsInterfaceImpl mPlayerSettingsInterface;

static jmethodID method_getCurrentSongInfo;
static jmethodID method_getPlaybackStatus;
static jmethodID method_sendMediaKeyEvent;
@@ -170,6 +227,11 @@ static jmethodID method_volumeDeviceDisconnected;

static jmethodID method_setVolume;

static jmethodID method_listPlayerSettings;
static jmethodID method_listPlayerSettingValues;
static jmethodID method_getPlayerSettings;
static jmethodID method_setPlayerSettings;

static void classInitNative(JNIEnv* env, jclass clazz) {
  method_getCurrentSongInfo = env->GetMethodID(
      clazz, "getCurrentSongInfo", "()Lcom/android/bluetooth/audio_util/Metadata;");
@@ -212,6 +274,18 @@ static void classInitNative(JNIEnv* env, jclass clazz) {

  method_setVolume = env->GetMethodID(clazz, "setVolume", "(I)V");

  method_listPlayerSettings =
      env->GetMethodID(clazz, "listPlayerSettingsRequest", "()V");

  method_listPlayerSettingValues =
      env->GetMethodID(clazz, "listPlayerSettingValuesRequest", "(B)V");

  method_getPlayerSettings =
      env->GetMethodID(clazz, "getCurrentPlayerSettingValuesRequest", "([B)V");

  method_setPlayerSettings =
      env->GetMethodID(clazz, "setPlayerSettingsRequest", "([B[B)V");

  ALOGI("%s: AvrcpTargetJni initialized!", __func__);
}

@@ -222,7 +296,8 @@ static void initNative(JNIEnv* env, jobject object) {
  mJavaInterface = env->NewGlobalRef(object);

  sServiceInterface = getBluetoothInterface()->get_avrcp_service();
  sServiceInterface->Init(&mAvrcpInterface, &mVolumeInterface);
  sServiceInterface->Init(&mAvrcpInterface, &mVolumeInterface,
                          &mPlayerSettingsInterface);
}

static void registerBipServerNative(JNIEnv* env, jobject object,
@@ -900,6 +975,127 @@ static void setBipClientStatusNative(JNIEnv* env, jobject object,
  sServiceInterface->SetBipClientStatus(bdaddr, status);
}

// Called from native to list available player settings
static void listPlayerSettings(ListPlayerSettingsCb cb) {
  ALOGD("%s", __func__);
  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
  CallbackEnv sCallbackEnv(__func__);
  if (!sCallbackEnv.valid() || !mJavaInterface) return;

  list_player_settings_cb = std::move(cb);
  sCallbackEnv->CallVoidMethod(mJavaInterface, method_listPlayerSettings);
}

static void listPlayerSettingsResponseNative(JNIEnv* env, jobject object,
                                             jbyteArray attributes) {
  ALOGD("%s", __func__);

  std::vector<PlayerAttribute> attributes_vector;
  copyJavaArraytoCppVector(env, attributes, attributes_vector);

  list_player_settings_cb.Run(std::move(attributes_vector));
}

// Called from native to list available values for player setting
static void listPlayerSettingValues(PlayerAttribute attribute,
                                    ListPlayerSettingValuesCb cb) {
  ALOGD("%s", __func__);
  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
  CallbackEnv sCallbackEnv(__func__);
  if (!sCallbackEnv.valid() || !mJavaInterface) return;

  list_player_setting_values_cb = std::move(cb);
  sCallbackEnv->CallVoidMethod(mJavaInterface, method_listPlayerSettingValues,
                               (jbyte)attribute);
}

static void listPlayerSettingValuesResponseNative(JNIEnv* env, jobject object,
                                                  jbyte attribute,
                                                  jbyteArray values) {
  ALOGD("%s", __func__);
  PlayerAttribute player_attribute = static_cast<PlayerAttribute>(attribute);
  std::vector<uint8_t> values_vector;
  copyJavaArraytoCppVector(env, values, values_vector);
  list_player_setting_values_cb.Run(player_attribute, std::move(values_vector));
}

// Called from native to get current player settings
static void getPlayerSettings(std::vector<PlayerAttribute> attributes,
                              GetCurrentPlayerSettingValueCb cb) {
  ALOGD("%s", __func__);
  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
  CallbackEnv sCallbackEnv(__func__);
  if (!sCallbackEnv.valid() || !mJavaInterface) return;

  jbyteArray attributes_array = sCallbackEnv->NewByteArray(attributes.size());
  sCallbackEnv->SetByteArrayRegion(
      attributes_array, 0, attributes.size(),
      reinterpret_cast<const jbyte*>(attributes.data()));

  get_current_player_setting_value_cb = std::move(cb);
  sCallbackEnv->CallVoidMethod(mJavaInterface, method_getPlayerSettings,
                               attributes_array);
}

static void getPlayerSettingsResponseNative(JNIEnv* env, jobject object,
                                            jbyteArray attributes,
                                            jbyteArray values) {
  ALOGD("%s", __func__);
  std::vector<PlayerAttribute> attributes_vector;
  std::vector<uint8_t> values_vector;
  copyJavaArraytoCppVector(env, attributes, attributes_vector);
  copyJavaArraytoCppVector(env, values, values_vector);
  get_current_player_setting_value_cb.Run(std::move(attributes_vector),
                                          std::move(values_vector));
}

// Called from native to set current player settings
static void setPlayerSettings(std::vector<PlayerAttribute> attributes,
                              std::vector<uint8_t> values,
                              SetPlayerSettingValueCb cb) {
  ALOGD("%s", __func__);
  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
  CallbackEnv sCallbackEnv(__func__);
  if (!sCallbackEnv.valid() || !mJavaInterface) return;

  jbyteArray attributes_array = sCallbackEnv->NewByteArray(attributes.size());
  sCallbackEnv->SetByteArrayRegion(
      attributes_array, 0, attributes.size(),
      reinterpret_cast<const jbyte*>(attributes.data()));

  jbyteArray values_array = sCallbackEnv->NewByteArray(values.size());
  sCallbackEnv->SetByteArrayRegion(
      values_array, 0, values.size(),
      reinterpret_cast<const jbyte*>(values.data()));

  set_player_setting_value_cb = std::move(cb);

  sCallbackEnv->CallVoidMethod(mJavaInterface, method_setPlayerSettings,
                               attributes_array, values_array);
}

static void setPlayerSettingsResponseNative(JNIEnv* env, jobject object,
                                            jboolean success) {
  ALOGD("%s", __func__);
  set_player_setting_value_cb.Run(success);
}

static void sendPlayerSettingsNative(JNIEnv* env, jobject object,
                                     jbyteArray attributes, jbyteArray values) {
  ALOGD("%s", __func__);
  std::unique_lock<std::shared_timed_mutex> interface_lock(interface_mutex);
  if (mServiceCallbacks == nullptr) {
    ALOGW("%s: Service not loaded.", __func__);
    return;
  }
  std::vector<PlayerAttribute> attributes_vector;
  std::vector<uint8_t> values_vector;
  copyJavaArraytoCppVector(env, attributes, attributes_vector);
  copyJavaArraytoCppVector(env, values, values_vector);
  mServiceCallbacks->SendPlayerSettingsChanged(attributes_vector,
                                               values_vector);
}

static JNINativeMethod sMethods[] = {
    {"classInitNative", "()V", (void*)classInitNative},
    {"initNative", "()V", (void*)initNative},
@@ -920,6 +1116,15 @@ static JNINativeMethod sMethods[] = {
     (void*)sendVolumeChangedNative},
    {"setBipClientStatusNative", "(Ljava/lang/String;Z)V",
     (void*)setBipClientStatusNative},
    {"listPlayerSettingsResponseNative", "([B)V",
     (void*)listPlayerSettingsResponseNative},
    {"listPlayerSettingValuesResponseNative", "(B[B)V",
     (void*)listPlayerSettingValuesResponseNative},
    {"getPlayerSettingsResponseNative", "([B[B)V",
     (void*)getPlayerSettingsResponseNative},
    {"setPlayerSettingsResponseNative", "(Z)V",
     (void*)setPlayerSettingsResponseNative},
    {"sendPlayerSettingsNative", "([B[B)V", (void*)sendPlayerSettingsNative},
};

int register_com_android_bluetooth_avrcp_target(JNIEnv* env) {
+26 −1
Original line number Diff line number Diff line
@@ -108,6 +108,8 @@ public class MediaPlayerList {

    private BrowsablePlayerConnector mBrowsablePlayerConnector;

    private MediaPlayerSettingsEventListener mPlayerSettingsListener;

    public interface MediaUpdateCallback {
        void run(MediaData data);
        void run(boolean availablePlayers, boolean addressedPlayers, boolean uids);
@@ -121,6 +123,16 @@ public class MediaPlayerList {
        void run(String parentId, List<ListItem> items);
    }

    /**
     * Listener for PlayerSettingsManager.
     */
    public interface MediaPlayerSettingsEventListener {
        /**
         * Called when the active player has changed.
         */
        void onActivePlayerChanged(MediaPlayerWrapper player);
    }

    public MediaPlayerList(Looper looper, Context context) {
        Log.v(TAG, "Creating MediaPlayerList");

@@ -633,6 +645,10 @@ public class MediaPlayerList {
        mActivePlayerLogger.logd(TAG, "setActivePlayer(): setting player to "
                + getActivePlayer().getPackageName());

        if (mPlayerSettingsListener != null) {
            mPlayerSettingsListener.onActivePlayerChanged(getActivePlayer());
        }

        // Ensure that metadata is synced on the new player
        if (!getActivePlayer().isMetadataSynced()) {
            Log.w(TAG, "setActivePlayer(): Metadata not synced on new player");
@@ -696,7 +712,12 @@ public class MediaPlayerList {
            synchronized (MediaPlayerList.this) {
                Log.v(TAG, "onActiveSessionsChanged: number of controllers: "
                        + newControllers.size());
                if (newControllers.size() == 0) return;
                if (newControllers.size() == 0) {
                    if (mPlayerSettingsListener != null) {
                        mPlayerSettingsListener.onActivePlayerChanged(null);
                    }
                    return;
                }

                // Apps are allowed to have multiple MediaControllers. If an app does have
                // multiple controllers then newControllers contains them in highest
@@ -797,6 +818,10 @@ public class MediaPlayerList {
        updateMediaForAudioPlayback();
    }

    void setPlayerSettingsCallback(MediaPlayerSettingsEventListener listener) {
        mPlayerSettingsListener = listener;
    }

    private final AudioManager.AudioPlaybackCallback mAudioPlaybackCallback =
            new AudioManager.AudioPlaybackCallback() {
        @Override
+4 −0
Original line number Diff line number Diff line
@@ -108,6 +108,10 @@ public class MediaPlayerWrapper {
        return mPackageName;
    }

    public MediaSession.Token getSessionToken() {
        return mMediaController.getSessionToken();
    }

    protected List<MediaSession.QueueItem> getQueue() {
        return mMediaController.getQueue();
    }
+271 −0
Original line number Diff line number Diff line
/*
 * Copyright 2023 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.audio_util;

import android.support.v4.media.session.MediaControllerCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;

import com.android.bluetooth.avrcp.AvrcpTargetService;

/**
 * Manager class for player apps.
 */
public class PlayerSettingsManager {
    private static final String TAG = "PlayerSettingsManager";

    private final MediaPlayerList mMediaPlayerList;
    private final AvrcpTargetService mService;

    private MediaControllerCompat mActivePlayerController;
    private final MediaControllerCallback mControllerCallback;

    /**
     * Instantiates a new PlayerSettingsManager.
     *
     * @param mediaPlayerList is used to retrieve the current active player.
     */
    public PlayerSettingsManager(MediaPlayerList mediaPlayerList, AvrcpTargetService service) {
        mService = service;
        mMediaPlayerList = mediaPlayerList;
        mMediaPlayerList.setPlayerSettingsCallback(
                (mediaPlayerWrapper) -> activePlayerChanged(mediaPlayerWrapper));
        mControllerCallback = new MediaControllerCallback();

        MediaPlayerWrapper wrapper = mMediaPlayerList.getActivePlayer();
        if (wrapper != null) {
            mActivePlayerController = new MediaControllerCompat(mService,
                    MediaSessionCompat.Token.fromToken(wrapper.getSessionToken()));
            mActivePlayerController.registerCallback(mControllerCallback);
        } else {
            mActivePlayerController = null;
        }
    }

    /**
     * Unregister callbacks
     */
    public void cleanup() {
        updateRemoteDevice();
        if (mActivePlayerController != null) {
            mActivePlayerController.unregisterCallback(mControllerCallback);
        }
    }

    /**
     * Updates the active player controller.
     */
    private void activePlayerChanged(MediaPlayerWrapper mediaPlayerWrapper) {
        if (mActivePlayerController != null) {
            mActivePlayerController.unregisterCallback(mControllerCallback);
        }
        if (mediaPlayerWrapper != null) {
            mActivePlayerController = new MediaControllerCompat(mService,
                    MediaSessionCompat.Token.fromToken(mediaPlayerWrapper.getSessionToken()));
            mActivePlayerController.registerCallback(new MediaControllerCallback());
        } else {
            mActivePlayerController = null;
            updateRemoteDevice();
        }
    }

    /**
     * Sends the MediaController values of the active player to the remote device.
     *
     * This is called when:
     * - The class is created and the session is ready
     * - The class is destroyed
     * - The active player changed and the session is ready
     * - The last active player has been removed
     * - The repeat / shuffle player state changed
     */
    private void updateRemoteDevice() {
        mService.sendPlayerSettings(getPlayerRepeatMode(), getPlayerShuffleMode());
    }

    /**
     * Called from remote device to set the active player repeat mode.
     */
    public boolean setPlayerRepeatMode(int repeatMode) {
        if (mActivePlayerController == null) {
            return false;
        }
        MediaControllerCompat.TransportControls controls =
                mActivePlayerController.getTransportControls();
        switch (repeatMode) {
            case PlayerSettingsValues.STATE_REPEAT_OFF:
                controls.setRepeatMode(PlaybackStateCompat.REPEAT_MODE_NONE);
                return true;
            case PlayerSettingsValues.STATE_REPEAT_SINGLE_TRACK:
                controls.setRepeatMode(PlaybackStateCompat.REPEAT_MODE_ONE);
                return true;
            case PlayerSettingsValues.STATE_REPEAT_GROUP:
                controls.setRepeatMode(PlaybackStateCompat.REPEAT_MODE_GROUP);
                return true;
            case PlayerSettingsValues.STATE_REPEAT_ALL_TRACK:
                controls.setRepeatMode(PlaybackStateCompat.REPEAT_MODE_ALL);
                return true;
            default:
                controls.setRepeatMode(PlaybackStateCompat.REPEAT_MODE_NONE);
                return false;
        }
    }

    /**
     * Called from remote device to set the active player shuffle mode.
     */
    public boolean setPlayerShuffleMode(int shuffleMode) {
        if (mActivePlayerController == null) {
            return false;
        }
        MediaControllerCompat.TransportControls controls =
                mActivePlayerController.getTransportControls();
        switch (shuffleMode) {
            case PlayerSettingsValues.STATE_SHUFFLE_OFF:
                controls.setShuffleMode(PlaybackStateCompat.SHUFFLE_MODE_NONE);
                return true;
            case PlayerSettingsValues.STATE_SHUFFLE_GROUP:
                controls.setShuffleMode(PlaybackStateCompat.SHUFFLE_MODE_GROUP);
                return true;
            case PlayerSettingsValues.STATE_SHUFFLE_ALL_TRACK:
                controls.setShuffleMode(PlaybackStateCompat.SHUFFLE_MODE_ALL);
                return true;
            default:
                controls.setShuffleMode(PlaybackStateCompat.SHUFFLE_MODE_NONE);
                return false;
        }
    }

    /**
     * Retrieves & converts the repeat value of the active player MediaController to AVRCP values
     */
    public int getPlayerRepeatMode() {
        if (mActivePlayerController == null) {
            return PlayerSettingsValues.STATE_REPEAT_OFF;
        }
        int mediaFwkMode = mActivePlayerController.getRepeatMode();
        switch (mediaFwkMode) {
            case PlaybackStateCompat.REPEAT_MODE_NONE:
                return PlayerSettingsValues.STATE_REPEAT_OFF;
            case PlaybackStateCompat.REPEAT_MODE_ONE:
                return PlayerSettingsValues.STATE_REPEAT_SINGLE_TRACK;
            case PlaybackStateCompat.REPEAT_MODE_GROUP:
                return PlayerSettingsValues.STATE_REPEAT_GROUP;
            case PlaybackStateCompat.REPEAT_MODE_ALL:
                return PlayerSettingsValues.STATE_REPEAT_ALL_TRACK;
            case PlaybackStateCompat.REPEAT_MODE_INVALID:
                return PlayerSettingsValues.STATE_REPEAT_OFF;
            default:
                return PlayerSettingsValues.STATE_REPEAT_OFF;
        }
    }

    /**
     * Retrieves & converts the shuffle value of the active player MediaController to AVRCP values
     */
    public int getPlayerShuffleMode() {
        if (mActivePlayerController == null) {
            return PlayerSettingsValues.STATE_SHUFFLE_OFF;
        }
        int mediaFwkMode = mActivePlayerController.getShuffleMode();
        switch (mediaFwkMode) {
            case PlaybackStateCompat.SHUFFLE_MODE_NONE:
                return PlayerSettingsValues.STATE_SHUFFLE_OFF;
            case PlaybackStateCompat.SHUFFLE_MODE_GROUP:
                return PlayerSettingsValues.STATE_SHUFFLE_GROUP;
            case PlaybackStateCompat.SHUFFLE_MODE_ALL:
                return PlayerSettingsValues.STATE_SHUFFLE_ALL_TRACK;
            case PlaybackStateCompat.SHUFFLE_MODE_INVALID:
                return PlayerSettingsValues.STATE_SHUFFLE_OFF;
            default:
                return PlayerSettingsValues.STATE_SHUFFLE_OFF;
        }
    }

    // Receives callbacks from the MediaControllerCompat.
    private class MediaControllerCallback extends MediaControllerCompat.Callback {
        @Override
        public void onRepeatModeChanged(final int repeatMode) {
            updateRemoteDevice();
        }

        @Override
        public void onSessionReady() {
            updateRemoteDevice();
        }

        @Override
        public void onShuffleModeChanged(final int shuffleMode) {
            updateRemoteDevice();
        }
    }

    /**
     * Class containing all the Shuffle/Repeat values as defined in the BT spec.
     */
    public static final class PlayerSettingsValues {
        /**
         * Repeat setting, as defined by Bluetooth specification.
         */
        public static final int SETTING_REPEAT = 2;

        /**
         * Shuffle setting, as defined by Bluetooth specification.
         */
        public static final int SETTING_SHUFFLE = 3;

        /**
         * Repeat OFF state, as defined by Bluetooth specification.
         */
        public static final int STATE_REPEAT_OFF = 1;

        /**
         * Single track repeat, as defined by Bluetooth specification.
         */
        public static final int STATE_REPEAT_SINGLE_TRACK = 2;

        /**
         * All track repeat, as defined by Bluetooth specification.
         */
        public static final int STATE_REPEAT_ALL_TRACK = 3;

        /**
         * Group repeat, as defined by Bluetooth specification.
         */
        public static final int STATE_REPEAT_GROUP = 4;

        /**
         * Shuffle OFF state, as defined by Bluetooth specification.
         */
        public static final int STATE_SHUFFLE_OFF = 1;

        /**
         * All track shuffle, as defined by Bluetooth specification.
         */
        public static final int STATE_SHUFFLE_ALL_TRACK = 2;

        /**
         * Group shuffle, as defined by Bluetooth specification.
         */
        public static final int STATE_SHUFFLE_GROUP = 3;

        /**
         * Default state off.
         */
        public static final int STATE_DEFAULT_OFF = 1;
    }
}
+106 −0

File changed.

Preview size limit exceeded, changes collapsed.

Loading