Loading android/app/jni/com_android_bluetooth_avrcp_target.cpp +206 −1 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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. Loading Loading @@ -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; Loading @@ -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;"); Loading Loading @@ -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__); } Loading @@ -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, Loading Loading @@ -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}, Loading @@ -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) { Loading android/app/src/com/android/bluetooth/audio_util/MediaPlayerList.java +26 −1 Original line number Diff line number Diff line Loading @@ -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); Loading @@ -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"); Loading Loading @@ -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"); Loading Loading @@ -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 Loading Loading @@ -797,6 +818,10 @@ public class MediaPlayerList { updateMediaForAudioPlayback(); } void setPlayerSettingsCallback(MediaPlayerSettingsEventListener listener) { mPlayerSettingsListener = listener; } private final AudioManager.AudioPlaybackCallback mAudioPlaybackCallback = new AudioManager.AudioPlaybackCallback() { @Override Loading android/app/src/com/android/bluetooth/audio_util/MediaPlayerWrapper.java +4 −0 Original line number Diff line number Diff line Loading @@ -108,6 +108,10 @@ public class MediaPlayerWrapper { return mPackageName; } public MediaSession.Token getSessionToken() { return mMediaController.getSessionToken(); } protected List<MediaSession.QueueItem> getQueue() { return mMediaController.getQueue(); } Loading android/app/src/com/android/bluetooth/audio_util/PlayerSettingsManager.java 0 → 100644 +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; } } android/app/src/com/android/bluetooth/avrcp/AvrcpNativeInterface.java +106 −0 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
android/app/jni/com_android_bluetooth_avrcp_target.cpp +206 −1 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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. Loading Loading @@ -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; Loading @@ -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;"); Loading Loading @@ -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__); } Loading @@ -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, Loading Loading @@ -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}, Loading @@ -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) { Loading
android/app/src/com/android/bluetooth/audio_util/MediaPlayerList.java +26 −1 Original line number Diff line number Diff line Loading @@ -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); Loading @@ -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"); Loading Loading @@ -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"); Loading Loading @@ -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 Loading Loading @@ -797,6 +818,10 @@ public class MediaPlayerList { updateMediaForAudioPlayback(); } void setPlayerSettingsCallback(MediaPlayerSettingsEventListener listener) { mPlayerSettingsListener = listener; } private final AudioManager.AudioPlaybackCallback mAudioPlaybackCallback = new AudioManager.AudioPlaybackCallback() { @Override Loading
android/app/src/com/android/bluetooth/audio_util/MediaPlayerWrapper.java +4 −0 Original line number Diff line number Diff line Loading @@ -108,6 +108,10 @@ public class MediaPlayerWrapper { return mPackageName; } public MediaSession.Token getSessionToken() { return mMediaController.getSessionToken(); } protected List<MediaSession.QueueItem> getQueue() { return mMediaController.getQueue(); } Loading
android/app/src/com/android/bluetooth/audio_util/PlayerSettingsManager.java 0 → 100644 +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; } }
android/app/src/com/android/bluetooth/avrcp/AvrcpNativeInterface.java +106 −0 File changed.Preview size limit exceeded, changes collapsed. Show changes