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

Commit 31bfa063 authored by Treehugger Robot's avatar Treehugger Robot Committed by Automerger Merge Worker
Browse files

Merge "Support for AVRCP Player Application settings" am: 3cbb18c7 am: f058d9ef

parents d8db42e0 f058d9ef
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