Loading android/app/jni/com_android_bluetooth_a2dp.cpp +67 −35 Original line number Diff line number Diff line Loading @@ -36,15 +36,23 @@ #include "types/raw_address.h" namespace android { static jmethodID method_onConnectionStateChanged; static jmethodID method_onAudioStateChanged; static jmethodID method_onCodecConfigChanged; static jmethodID method_isMandatoryCodecPreferred; static struct { jfieldID mNativeCallback; } android_bluetooth_A2dpNativeInterface; static struct { jmethodID onConnectionStateChanged; jmethodID onAudioStateChanged; jmethodID onCodecConfigChanged; jmethodID isMandatoryCodecPreferred; } android_bluetooth_A2dpNativeCallback; static struct { jclass clazz; jmethodID constructor; jmethodID getCodecType; jmethodID getExtendedCodecType; jmethodID getCodecPriority; jmethodID getSampleRate; jmethodID getBitsPerSample; Loading @@ -55,6 +63,12 @@ static struct { jmethodID getCodecSpecific4; } android_bluetooth_BluetoothCodecConfig; static struct { jclass clazz; jmethodID constructor; jmethodID getCodecId; } android_bluetooth_BluetoothCodecType; static std::vector<btav_a2dp_codec_info_t> supported_codecs; static std::shared_timed_mutex interface_mutex; Loading @@ -81,8 +95,9 @@ static void bta2dp_connection_state_callback(const RawAddress& bd_addr, sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress), reinterpret_cast<const jbyte*>(bd_addr.address)); sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onConnectionStateChanged, addr.get(), (jint)state); sCallbackEnv->CallVoidMethod(mCallbacksObj, android_bluetooth_A2dpNativeCallback.onConnectionStateChanged, addr.get(), (jint)state); } static void bta2dp_audio_state_callback(const RawAddress& bd_addr, btav_audio_state_t state) { Loading @@ -103,7 +118,9 @@ static void bta2dp_audio_state_callback(const RawAddress& bd_addr, btav_audio_st sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress), reinterpret_cast<const jbyte*>(bd_addr.address)); sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAudioStateChanged, addr.get(), (jint)state); sCallbackEnv->CallVoidMethod(mCallbacksObj, android_bluetooth_A2dpNativeCallback.onAudioStateChanged, addr.get(), (jint)state); } static void bta2dp_audio_config_callback( Loading Loading @@ -168,9 +185,9 @@ static void bta2dp_audio_config_callback( sCallbackEnv->SetByteArrayRegion(addr.get(), 0, RawAddress::kLength, reinterpret_cast<const jbyte*>(bd_addr.address)); sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onCodecConfigChanged, addr.get(), codecConfigObj, local_capabilities_array, selectable_capabilities_array); sCallbackEnv->CallVoidMethod( mCallbacksObj, android_bluetooth_A2dpNativeCallback.onCodecConfigChanged, addr.get(), codecConfigObj, local_capabilities_array, selectable_capabilities_array); } static bool bta2dp_mandatory_codec_preferred_callback(const RawAddress& bd_addr) { Loading @@ -190,7 +207,8 @@ static bool bta2dp_mandatory_codec_preferred_callback(const RawAddress& bd_addr) } sCallbackEnv->SetByteArrayRegion(addr.get(), 0, RawAddress::kLength, reinterpret_cast<const jbyte*>(bd_addr.address)); return sCallbackEnv->CallBooleanMethod(mCallbacksObj, method_isMandatoryCodecPreferred, return sCallbackEnv->CallBooleanMethod( mCallbacksObj, android_bluetooth_A2dpNativeCallback.isMandatoryCodecPreferred, addr.get()); } Loading @@ -216,6 +234,7 @@ static std::vector<btav_a2dp_codec_config_t> prepareCodecPreferences( log::error("Invalid BluetoothCodecConfig instance"); continue; } jint codecType = env->CallIntMethod(jcodecConfig, android_bluetooth_BluetoothCodecConfig.getCodecType); jint codecPriority = env->CallIntMethod( Loading Loading @@ -268,7 +287,8 @@ static void initNative(JNIEnv* env, jobject object, jint maxConnectedAudioDevice mCallbacksObj = nullptr; } if ((mCallbacksObj = env->NewGlobalRef(object)) == nullptr) { if ((mCallbacksObj = env->NewGlobalRef(env->GetObjectField( object, android_bluetooth_A2dpNativeInterface.mNativeCallback))) == nullptr) { log::error("Failed to allocate Global Ref for A2DP Callbacks"); return; } Loading @@ -280,6 +300,13 @@ static void initNative(JNIEnv* env, jobject object, jint maxConnectedAudioDevice return; } android_bluetooth_BluetoothCodecType.clazz = (jclass)env->NewGlobalRef(env->FindClass("android/bluetooth/BluetoothCodecType")); if (android_bluetooth_BluetoothCodecType.clazz == nullptr) { log::error("Failed to allocate Global Ref for BluetoothCodecType class"); return; } std::vector<btav_a2dp_codec_config_t> codec_priorities = prepareCodecPreferences(env, object, codecConfigArray); Loading Loading @@ -316,23 +343,8 @@ static void cleanupNative(JNIEnv* env, jobject /* object */) { } static jobjectArray getSupportedCodecTypesNative(JNIEnv* env) { jclass android_bluetooth_BluetoothCodecType_clazz = (jclass)env->NewGlobalRef(env->FindClass("android/bluetooth/BluetoothCodecType")); if (android_bluetooth_BluetoothCodecType_clazz == nullptr) { log::error("Failed to allocate Global Ref for BluetoothCodecType class"); return nullptr; } jmethodID init = env->GetMethodID(android_bluetooth_BluetoothCodecType_clazz, "<init>", "(IJLjava/lang/String;)V"); if (init == nullptr) { log::error("Failed to find method <init> of BluetoothCodecType class"); return nullptr; } jobjectArray result = env->NewObjectArray(supported_codecs.size(), android_bluetooth_BluetoothCodecType_clazz, nullptr); android_bluetooth_BluetoothCodecType.clazz, nullptr); if (result == nullptr) { log::error("Failed to allocate result array of BluetoothCodecType"); Loading @@ -341,7 +353,8 @@ static jobjectArray getSupportedCodecTypesNative(JNIEnv* env) { for (size_t index = 0; index < supported_codecs.size(); index++) { jobject codec_type = env->NewObject( android_bluetooth_BluetoothCodecType_clazz, init, android_bluetooth_BluetoothCodecType.clazz, android_bluetooth_BluetoothCodecType.constructor, (jint)supported_codecs[index].codec_type, (jlong)supported_codecs[index].codec_id, env->NewStringUTF(supported_codecs[index].codec_name.c_str())); env->SetObjectArrayElement(result, index, codec_type); Loading Loading @@ -443,6 +456,7 @@ static jboolean setCodecConfigPreferenceNative(JNIEnv* env, jobject object, jbyt bd_addr.FromOctets(reinterpret_cast<const uint8_t*>(addr)); std::vector<btav_a2dp_codec_config_t> codec_preferences = prepareCodecPreferences(env, object, codecConfigArray); log::info("{}: {}", bd_addr, btav_a2dp_codec_config_t::PrintCodecs(codec_preferences)); bt_status_t status = btif_av_source_set_codec_config_preference(bd_addr, codec_preferences); if (status != BT_STATUS_SUCCESS) { Loading Loading @@ -475,20 +489,32 @@ int register_com_android_bluetooth_a2dp(JNIEnv* env) { } const JNIJavaMethod javaMethods[] = { {"onConnectionStateChanged", "([BI)V", &method_onConnectionStateChanged}, {"onAudioStateChanged", "([BI)V", &method_onAudioStateChanged}, {"onConnectionStateChanged", "([BI)V", &android_bluetooth_A2dpNativeCallback.onConnectionStateChanged}, {"onAudioStateChanged", "([BI)V", &android_bluetooth_A2dpNativeCallback.onAudioStateChanged}, {"onCodecConfigChanged", "([BLandroid/bluetooth/BluetoothCodecConfig;" "[Landroid/bluetooth/BluetoothCodecConfig;" "[Landroid/bluetooth/BluetoothCodecConfig;)V", &method_onCodecConfigChanged}, {"isMandatoryCodecPreferred", "([B)Z", &method_isMandatoryCodecPreferred}, &android_bluetooth_A2dpNativeCallback.onCodecConfigChanged}, {"isMandatoryCodecPreferred", "([B)Z", &android_bluetooth_A2dpNativeCallback.isMandatoryCodecPreferred}, }; GET_JAVA_METHODS(env, "com/android/bluetooth/a2dp/A2dpNativeInterface", javaMethods); GET_JAVA_METHODS(env, "com/android/bluetooth/a2dp/A2dpNativeCallback", javaMethods); jclass jniA2dpNativeInterfaceClass = env->FindClass("com/android/bluetooth/a2dp/A2dpNativeInterface"); android_bluetooth_A2dpNativeInterface.mNativeCallback = env->GetFieldID(jniA2dpNativeInterfaceClass, "mNativeCallback", "Lcom/android/bluetooth/a2dp/A2dpNativeCallback;"); env->DeleteLocalRef(jniA2dpNativeInterfaceClass); const JNIJavaMethod codecConfigCallbacksMethods[] = { {"<init>", "(IIIIIJJJJ)V", &android_bluetooth_BluetoothCodecConfig.constructor}, {"getCodecType", "()I", &android_bluetooth_BluetoothCodecConfig.getCodecType}, {"getExtendedCodecType", "()Landroid/bluetooth/BluetoothCodecType;", &android_bluetooth_BluetoothCodecConfig.getExtendedCodecType}, {"getCodecPriority", "()I", &android_bluetooth_BluetoothCodecConfig.getCodecPriority}, {"getSampleRate", "()I", &android_bluetooth_BluetoothCodecConfig.getSampleRate}, {"getBitsPerSample", "()I", &android_bluetooth_BluetoothCodecConfig.getBitsPerSample}, Loading @@ -500,6 +526,12 @@ int register_com_android_bluetooth_a2dp(JNIEnv* env) { }; GET_JAVA_METHODS(env, "android/bluetooth/BluetoothCodecConfig", codecConfigCallbacksMethods); const JNIJavaMethod bluetoothCodecTypeMethods[] = { {"<init>", "(IJLjava/lang/String;)V", &android_bluetooth_BluetoothCodecType.constructor}, {"getCodecId", "()J", &android_bluetooth_BluetoothCodecType.getCodecId}, }; GET_JAVA_METHODS(env, "android/bluetooth/BluetoothCodecType", bluetoothCodecTypeMethods); return 0; } } // namespace android android/app/src/com/android/bluetooth/a2dp/A2dpNativeCallback.java 0 → 100644 +95 −0 Original line number Diff line number Diff line /* * Copyright 2024 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.a2dp; import static java.util.Objects.requireNonNull; import android.annotation.NonNull; import android.bluetooth.BluetoothA2dp; import android.bluetooth.BluetoothCodecConfig; import android.bluetooth.BluetoothCodecStatus; import android.bluetooth.BluetoothDevice; import android.util.Log; import com.android.bluetooth.btservice.AdapterService; import com.android.internal.annotations.VisibleForTesting; import java.util.Arrays; class A2dpNativeCallback { static final String TAG = A2dpNativeCallback.class.getSimpleName(); private final AdapterService mAdapterService; private final A2dpService mA2dpService; @VisibleForTesting A2dpNativeCallback(@NonNull AdapterService adapterService, @NonNull A2dpService a2dpService) { mAdapterService = requireNonNull(adapterService); mA2dpService = requireNonNull(a2dpService); } private BluetoothDevice getDevice(byte[] address) { return mAdapterService.getDeviceFromByte(address); } @VisibleForTesting void onConnectionStateChanged(byte[] address, int state) { A2dpStackEvent event = new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); event.device = getDevice(address); event.valueInt = state; Log.d(TAG, "onConnectionStateChanged: " + event); mA2dpService.messageFromNative(event); } @VisibleForTesting void onAudioStateChanged(byte[] address, int state) { A2dpStackEvent event = new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED); event.device = getDevice(address); event.valueInt = state; Log.d(TAG, "onAudioStateChanged: " + event); mA2dpService.messageFromNative(event); } @VisibleForTesting void onCodecConfigChanged( byte[] address, BluetoothCodecConfig newCodecConfig, BluetoothCodecConfig[] codecsLocalCapabilities, BluetoothCodecConfig[] codecsSelectableCapabilities) { A2dpStackEvent event = new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CODEC_CONFIG_CHANGED); event.device = getDevice(address); event.codecStatus = new BluetoothCodecStatus( newCodecConfig, Arrays.asList(codecsLocalCapabilities), Arrays.asList(codecsSelectableCapabilities)); Log.d(TAG, "onCodecConfigChanged: " + event); mA2dpService.messageFromNative(event); } @VisibleForTesting boolean isMandatoryCodecPreferred(byte[] address) { int enabled = mA2dpService.getOptionalCodecsEnabled(getDevice(address)); Log.d(TAG, "isMandatoryCodecPreferred: optional preference " + enabled); return enabled == BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED; } } android/app/src/com/android/bluetooth/a2dp/A2dpNativeInterface.java +15 −109 Original line number Diff line number Diff line Loading @@ -21,65 +21,37 @@ */ package com.android.bluetooth.a2dp; import android.bluetooth.BluetoothA2dp; import android.bluetooth.BluetoothAdapter; import static java.util.Objects.requireNonNull; import android.annotation.NonNull; import android.bluetooth.BluetoothCodecConfig; import android.bluetooth.BluetoothCodecStatus; import android.bluetooth.BluetoothCodecType; import android.bluetooth.BluetoothDevice; import android.util.Log; import com.android.bluetooth.Utils; import com.android.bluetooth.btservice.AdapterService; import com.android.bluetooth.flags.Flags; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import java.lang.annotation.Native; import java.util.Arrays; import java.util.List; import java.util.Objects; /** A2DP Native Interface to/from JNI. */ public class A2dpNativeInterface { private static final String TAG = A2dpNativeInterface.class.getSimpleName(); private BluetoothAdapter mAdapter; private AdapterService mAdapterService; @GuardedBy("INSTANCE_LOCK") private static A2dpNativeInterface sInstance; private static BluetoothCodecType[] sSupportedCodecTypes; private static final Object INSTANCE_LOCK = new Object(); @VisibleForTesting private A2dpNativeInterface() { mAdapter = BluetoothAdapter.getDefaultAdapter(); if (mAdapter == null) { Log.wtf(TAG, "No Bluetooth Adapter Available"); } mAdapterService = Objects.requireNonNull( AdapterService.getAdapterService(), "AdapterService cannot be null when A2dpNativeInterface init"); } private final AdapterService mAdapterService; @Native private final A2dpNativeCallback mNativeCallback; /** Get singleton instance. */ public static A2dpNativeInterface getInstance() { synchronized (INSTANCE_LOCK) { if (sInstance == null) { sInstance = new A2dpNativeInterface(); } return sInstance; } } private BluetoothCodecType[] mSupportedCodecTypes; /** Set singleton instance. */ @VisibleForTesting public static void setInstance(A2dpNativeInterface instance) { synchronized (INSTANCE_LOCK) { sInstance = instance; } A2dpNativeInterface( @NonNull AdapterService adapterService, @NonNull A2dpNativeCallback nativeCallback) { mAdapterService = requireNonNull(adapterService); mNativeCallback = requireNonNull(nativeCallback); } /** Loading @@ -103,10 +75,10 @@ public class A2dpNativeInterface { /** Returns the list of locally supported codec types. */ public List<BluetoothCodecType> getSupportedCodecTypes() { if (sSupportedCodecTypes == null) { sSupportedCodecTypes = getSupportedCodecTypesNative(); if (mSupportedCodecTypes == null) { mSupportedCodecTypes = getSupportedCodecTypesNative(); } return Arrays.asList(sSupportedCodecTypes); return Arrays.asList(mSupportedCodecTypes); } /** Loading Loading @@ -161,10 +133,6 @@ public class A2dpNativeInterface { return setCodecConfigPreferenceNative(getByteAddress(device), codecConfigArray); } private BluetoothDevice getDevice(byte[] address) { return mAdapterService.getDeviceFromByte(address); } private byte[] getByteAddress(BluetoothDevice device) { if (device == null) { return Utils.getBytesFromAddress("00:00:00:00:00:00"); Loading @@ -176,68 +144,6 @@ public class A2dpNativeInterface { } } private void sendMessageToService(A2dpStackEvent event) { A2dpService service = A2dpService.getA2dpService(); if (service != null) { service.messageFromNative(event); } else { Log.w(TAG, "Event ignored, service not available: " + event); } } // Callbacks from the native stack back into the Java framework. // All callbacks are routed via the Service which will disambiguate which // state machine the message should be routed to. private void onConnectionStateChanged(byte[] address, int state) { A2dpStackEvent event = new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); event.device = getDevice(address); event.valueInt = state; Log.d(TAG, "onConnectionStateChanged: " + event); sendMessageToService(event); } private void onAudioStateChanged(byte[] address, int state) { A2dpStackEvent event = new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED); event.device = getDevice(address); event.valueInt = state; Log.d(TAG, "onAudioStateChanged: " + event); sendMessageToService(event); } private void onCodecConfigChanged( byte[] address, BluetoothCodecConfig newCodecConfig, BluetoothCodecConfig[] codecsLocalCapabilities, BluetoothCodecConfig[] codecsSelectableCapabilities) { A2dpStackEvent event = new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CODEC_CONFIG_CHANGED); event.device = getDevice(address); event.codecStatus = new BluetoothCodecStatus( newCodecConfig, Arrays.asList(codecsLocalCapabilities), Arrays.asList(codecsSelectableCapabilities)); Log.d(TAG, "onCodecConfigChanged: " + event); sendMessageToService(event); } private boolean isMandatoryCodecPreferred(byte[] address) { A2dpService service = A2dpService.getA2dpService(); if (service != null) { int enabled = service.getOptionalCodecsEnabled(getDevice(address)); Log.d(TAG, "isMandatoryCodecPreferred: optional preference " + enabled); // Optional codecs are more preferred if possible return enabled == BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED; } else { Log.w(TAG, "isMandatoryCodecPreferred: service not available"); return false; } } // Native methods that call into the JNI interface private native void initNative( int maxConnectedAudioDevices, BluetoothCodecConfig[] codecConfigPriorities, Loading android/app/src/com/android/bluetooth/a2dp/A2dpService.java +14 −4 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import static android.bluetooth.BluetoothProfile.STATE_CONNECTING; import static com.android.bluetooth.Utils.checkCallerTargetSdk; import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNullElseGet; import android.annotation.NonNull; import android.annotation.RequiresPermission; Loading Loading @@ -116,14 +117,20 @@ public class A2dpService extends ProfileService { new AudioManagerAudioDeviceCallback(); public A2dpService(AdapterService adapterService) { this(adapterService, A2dpNativeInterface.getInstance(), Looper.getMainLooper()); this(adapterService, null, Looper.getMainLooper()); } @VisibleForTesting A2dpService(AdapterService adapterService, A2dpNativeInterface nativeInterface, Looper looper) { super(requireNonNull(adapterService)); mAdapterService = adapterService; mNativeInterface = requireNonNull(nativeInterface); mNativeInterface = requireNonNullElseGet( nativeInterface, () -> new A2dpNativeInterface( adapterService, new A2dpNativeCallback(adapterService, this))); mDatabaseManager = requireNonNull(mAdapterService.getDatabase()); mAudioManager = requireNonNull(getSystemService(AudioManager.class)); mCompanionDeviceManager = requireNonNull(getSystemService(CompanionDeviceManager.class)); Loading Loading @@ -879,9 +886,12 @@ public class A2dpService extends ProfileService { // Handle messages from native (JNI) to Java void messageFromNative(A2dpStackEvent stackEvent) { requireNonNull(stackEvent.device); if (!isAvailable()) { Log.w(TAG, "messageFromNative(): service is not available"); return; } BluetoothDevice device = requireNonNull(stackEvent.device); synchronized (mStateMachines) { BluetoothDevice device = stackEvent.device; A2dpStateMachine sm = mStateMachines.get(device); if (sm == null) { if (stackEvent.type == A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED) { Loading android/app/tests/unit/src/com/android/bluetooth/btservice/ProfileServiceTest.java +2 −5 Original line number Diff line number Diff line Loading @@ -32,7 +32,6 @@ import androidx.test.filters.MediumTest; import androidx.test.runner.AndroidJUnit4; import com.android.bluetooth.TestUtils; import com.android.bluetooth.a2dp.A2dpNativeInterface; import com.android.bluetooth.a2dpsink.A2dpSinkNativeInterface; import com.android.bluetooth.avrcp.AvrcpNativeInterface; import com.android.bluetooth.avrcpcontroller.AvrcpControllerNativeInterface; Loading Loading @@ -80,7 +79,6 @@ public class ProfileServiceTest { private int[] mProfiles; @Mock private A2dpNativeInterface mA2dpNativeInterface; @Mock private A2dpSinkNativeInterface mA2dpSinkNativeInterface; @Mock private AvrcpNativeInterface mAvrcpNativeInterface; @Mock private AvrcpControllerNativeInterface mAvrcpControllerNativeInterface; Loading Loading @@ -155,13 +153,13 @@ public class ProfileServiceTest { profile != BluetoothProfile.HAP_CLIENT && profile != BluetoothProfile.VOLUME_CONTROL && profile != BluetoothProfile.CSIP_SET_COORDINATOR && profile != BluetoothProfile.GATT) && profile != BluetoothProfile.GATT && profile != BluetoothProfile.A2DP) .toArray(); TestUtils.setAdapterService(mAdapterService); Assert.assertNotNull(AdapterService.getAdapterService()); A2dpNativeInterface.setInstance(mA2dpNativeInterface); A2dpSinkNativeInterface.setInstance(mA2dpSinkNativeInterface); AvrcpNativeInterface.setInstance(mAvrcpNativeInterface); AvrcpControllerNativeInterface.setInstance(mAvrcpControllerNativeInterface); Loading @@ -180,7 +178,6 @@ public class ProfileServiceTest { TestUtils.clearAdapterService(mAdapterService); mAdapterService = null; mProfiles = null; A2dpNativeInterface.setInstance(null); A2dpSinkNativeInterface.setInstance(null); AvrcpNativeInterface.setInstance(null); AvrcpControllerNativeInterface.setInstance(null); Loading Loading
android/app/jni/com_android_bluetooth_a2dp.cpp +67 −35 Original line number Diff line number Diff line Loading @@ -36,15 +36,23 @@ #include "types/raw_address.h" namespace android { static jmethodID method_onConnectionStateChanged; static jmethodID method_onAudioStateChanged; static jmethodID method_onCodecConfigChanged; static jmethodID method_isMandatoryCodecPreferred; static struct { jfieldID mNativeCallback; } android_bluetooth_A2dpNativeInterface; static struct { jmethodID onConnectionStateChanged; jmethodID onAudioStateChanged; jmethodID onCodecConfigChanged; jmethodID isMandatoryCodecPreferred; } android_bluetooth_A2dpNativeCallback; static struct { jclass clazz; jmethodID constructor; jmethodID getCodecType; jmethodID getExtendedCodecType; jmethodID getCodecPriority; jmethodID getSampleRate; jmethodID getBitsPerSample; Loading @@ -55,6 +63,12 @@ static struct { jmethodID getCodecSpecific4; } android_bluetooth_BluetoothCodecConfig; static struct { jclass clazz; jmethodID constructor; jmethodID getCodecId; } android_bluetooth_BluetoothCodecType; static std::vector<btav_a2dp_codec_info_t> supported_codecs; static std::shared_timed_mutex interface_mutex; Loading @@ -81,8 +95,9 @@ static void bta2dp_connection_state_callback(const RawAddress& bd_addr, sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress), reinterpret_cast<const jbyte*>(bd_addr.address)); sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onConnectionStateChanged, addr.get(), (jint)state); sCallbackEnv->CallVoidMethod(mCallbacksObj, android_bluetooth_A2dpNativeCallback.onConnectionStateChanged, addr.get(), (jint)state); } static void bta2dp_audio_state_callback(const RawAddress& bd_addr, btav_audio_state_t state) { Loading @@ -103,7 +118,9 @@ static void bta2dp_audio_state_callback(const RawAddress& bd_addr, btav_audio_st sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress), reinterpret_cast<const jbyte*>(bd_addr.address)); sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAudioStateChanged, addr.get(), (jint)state); sCallbackEnv->CallVoidMethod(mCallbacksObj, android_bluetooth_A2dpNativeCallback.onAudioStateChanged, addr.get(), (jint)state); } static void bta2dp_audio_config_callback( Loading Loading @@ -168,9 +185,9 @@ static void bta2dp_audio_config_callback( sCallbackEnv->SetByteArrayRegion(addr.get(), 0, RawAddress::kLength, reinterpret_cast<const jbyte*>(bd_addr.address)); sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onCodecConfigChanged, addr.get(), codecConfigObj, local_capabilities_array, selectable_capabilities_array); sCallbackEnv->CallVoidMethod( mCallbacksObj, android_bluetooth_A2dpNativeCallback.onCodecConfigChanged, addr.get(), codecConfigObj, local_capabilities_array, selectable_capabilities_array); } static bool bta2dp_mandatory_codec_preferred_callback(const RawAddress& bd_addr) { Loading @@ -190,7 +207,8 @@ static bool bta2dp_mandatory_codec_preferred_callback(const RawAddress& bd_addr) } sCallbackEnv->SetByteArrayRegion(addr.get(), 0, RawAddress::kLength, reinterpret_cast<const jbyte*>(bd_addr.address)); return sCallbackEnv->CallBooleanMethod(mCallbacksObj, method_isMandatoryCodecPreferred, return sCallbackEnv->CallBooleanMethod( mCallbacksObj, android_bluetooth_A2dpNativeCallback.isMandatoryCodecPreferred, addr.get()); } Loading @@ -216,6 +234,7 @@ static std::vector<btav_a2dp_codec_config_t> prepareCodecPreferences( log::error("Invalid BluetoothCodecConfig instance"); continue; } jint codecType = env->CallIntMethod(jcodecConfig, android_bluetooth_BluetoothCodecConfig.getCodecType); jint codecPriority = env->CallIntMethod( Loading Loading @@ -268,7 +287,8 @@ static void initNative(JNIEnv* env, jobject object, jint maxConnectedAudioDevice mCallbacksObj = nullptr; } if ((mCallbacksObj = env->NewGlobalRef(object)) == nullptr) { if ((mCallbacksObj = env->NewGlobalRef(env->GetObjectField( object, android_bluetooth_A2dpNativeInterface.mNativeCallback))) == nullptr) { log::error("Failed to allocate Global Ref for A2DP Callbacks"); return; } Loading @@ -280,6 +300,13 @@ static void initNative(JNIEnv* env, jobject object, jint maxConnectedAudioDevice return; } android_bluetooth_BluetoothCodecType.clazz = (jclass)env->NewGlobalRef(env->FindClass("android/bluetooth/BluetoothCodecType")); if (android_bluetooth_BluetoothCodecType.clazz == nullptr) { log::error("Failed to allocate Global Ref for BluetoothCodecType class"); return; } std::vector<btav_a2dp_codec_config_t> codec_priorities = prepareCodecPreferences(env, object, codecConfigArray); Loading Loading @@ -316,23 +343,8 @@ static void cleanupNative(JNIEnv* env, jobject /* object */) { } static jobjectArray getSupportedCodecTypesNative(JNIEnv* env) { jclass android_bluetooth_BluetoothCodecType_clazz = (jclass)env->NewGlobalRef(env->FindClass("android/bluetooth/BluetoothCodecType")); if (android_bluetooth_BluetoothCodecType_clazz == nullptr) { log::error("Failed to allocate Global Ref for BluetoothCodecType class"); return nullptr; } jmethodID init = env->GetMethodID(android_bluetooth_BluetoothCodecType_clazz, "<init>", "(IJLjava/lang/String;)V"); if (init == nullptr) { log::error("Failed to find method <init> of BluetoothCodecType class"); return nullptr; } jobjectArray result = env->NewObjectArray(supported_codecs.size(), android_bluetooth_BluetoothCodecType_clazz, nullptr); android_bluetooth_BluetoothCodecType.clazz, nullptr); if (result == nullptr) { log::error("Failed to allocate result array of BluetoothCodecType"); Loading @@ -341,7 +353,8 @@ static jobjectArray getSupportedCodecTypesNative(JNIEnv* env) { for (size_t index = 0; index < supported_codecs.size(); index++) { jobject codec_type = env->NewObject( android_bluetooth_BluetoothCodecType_clazz, init, android_bluetooth_BluetoothCodecType.clazz, android_bluetooth_BluetoothCodecType.constructor, (jint)supported_codecs[index].codec_type, (jlong)supported_codecs[index].codec_id, env->NewStringUTF(supported_codecs[index].codec_name.c_str())); env->SetObjectArrayElement(result, index, codec_type); Loading Loading @@ -443,6 +456,7 @@ static jboolean setCodecConfigPreferenceNative(JNIEnv* env, jobject object, jbyt bd_addr.FromOctets(reinterpret_cast<const uint8_t*>(addr)); std::vector<btav_a2dp_codec_config_t> codec_preferences = prepareCodecPreferences(env, object, codecConfigArray); log::info("{}: {}", bd_addr, btav_a2dp_codec_config_t::PrintCodecs(codec_preferences)); bt_status_t status = btif_av_source_set_codec_config_preference(bd_addr, codec_preferences); if (status != BT_STATUS_SUCCESS) { Loading Loading @@ -475,20 +489,32 @@ int register_com_android_bluetooth_a2dp(JNIEnv* env) { } const JNIJavaMethod javaMethods[] = { {"onConnectionStateChanged", "([BI)V", &method_onConnectionStateChanged}, {"onAudioStateChanged", "([BI)V", &method_onAudioStateChanged}, {"onConnectionStateChanged", "([BI)V", &android_bluetooth_A2dpNativeCallback.onConnectionStateChanged}, {"onAudioStateChanged", "([BI)V", &android_bluetooth_A2dpNativeCallback.onAudioStateChanged}, {"onCodecConfigChanged", "([BLandroid/bluetooth/BluetoothCodecConfig;" "[Landroid/bluetooth/BluetoothCodecConfig;" "[Landroid/bluetooth/BluetoothCodecConfig;)V", &method_onCodecConfigChanged}, {"isMandatoryCodecPreferred", "([B)Z", &method_isMandatoryCodecPreferred}, &android_bluetooth_A2dpNativeCallback.onCodecConfigChanged}, {"isMandatoryCodecPreferred", "([B)Z", &android_bluetooth_A2dpNativeCallback.isMandatoryCodecPreferred}, }; GET_JAVA_METHODS(env, "com/android/bluetooth/a2dp/A2dpNativeInterface", javaMethods); GET_JAVA_METHODS(env, "com/android/bluetooth/a2dp/A2dpNativeCallback", javaMethods); jclass jniA2dpNativeInterfaceClass = env->FindClass("com/android/bluetooth/a2dp/A2dpNativeInterface"); android_bluetooth_A2dpNativeInterface.mNativeCallback = env->GetFieldID(jniA2dpNativeInterfaceClass, "mNativeCallback", "Lcom/android/bluetooth/a2dp/A2dpNativeCallback;"); env->DeleteLocalRef(jniA2dpNativeInterfaceClass); const JNIJavaMethod codecConfigCallbacksMethods[] = { {"<init>", "(IIIIIJJJJ)V", &android_bluetooth_BluetoothCodecConfig.constructor}, {"getCodecType", "()I", &android_bluetooth_BluetoothCodecConfig.getCodecType}, {"getExtendedCodecType", "()Landroid/bluetooth/BluetoothCodecType;", &android_bluetooth_BluetoothCodecConfig.getExtendedCodecType}, {"getCodecPriority", "()I", &android_bluetooth_BluetoothCodecConfig.getCodecPriority}, {"getSampleRate", "()I", &android_bluetooth_BluetoothCodecConfig.getSampleRate}, {"getBitsPerSample", "()I", &android_bluetooth_BluetoothCodecConfig.getBitsPerSample}, Loading @@ -500,6 +526,12 @@ int register_com_android_bluetooth_a2dp(JNIEnv* env) { }; GET_JAVA_METHODS(env, "android/bluetooth/BluetoothCodecConfig", codecConfigCallbacksMethods); const JNIJavaMethod bluetoothCodecTypeMethods[] = { {"<init>", "(IJLjava/lang/String;)V", &android_bluetooth_BluetoothCodecType.constructor}, {"getCodecId", "()J", &android_bluetooth_BluetoothCodecType.getCodecId}, }; GET_JAVA_METHODS(env, "android/bluetooth/BluetoothCodecType", bluetoothCodecTypeMethods); return 0; } } // namespace android
android/app/src/com/android/bluetooth/a2dp/A2dpNativeCallback.java 0 → 100644 +95 −0 Original line number Diff line number Diff line /* * Copyright 2024 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.a2dp; import static java.util.Objects.requireNonNull; import android.annotation.NonNull; import android.bluetooth.BluetoothA2dp; import android.bluetooth.BluetoothCodecConfig; import android.bluetooth.BluetoothCodecStatus; import android.bluetooth.BluetoothDevice; import android.util.Log; import com.android.bluetooth.btservice.AdapterService; import com.android.internal.annotations.VisibleForTesting; import java.util.Arrays; class A2dpNativeCallback { static final String TAG = A2dpNativeCallback.class.getSimpleName(); private final AdapterService mAdapterService; private final A2dpService mA2dpService; @VisibleForTesting A2dpNativeCallback(@NonNull AdapterService adapterService, @NonNull A2dpService a2dpService) { mAdapterService = requireNonNull(adapterService); mA2dpService = requireNonNull(a2dpService); } private BluetoothDevice getDevice(byte[] address) { return mAdapterService.getDeviceFromByte(address); } @VisibleForTesting void onConnectionStateChanged(byte[] address, int state) { A2dpStackEvent event = new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); event.device = getDevice(address); event.valueInt = state; Log.d(TAG, "onConnectionStateChanged: " + event); mA2dpService.messageFromNative(event); } @VisibleForTesting void onAudioStateChanged(byte[] address, int state) { A2dpStackEvent event = new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED); event.device = getDevice(address); event.valueInt = state; Log.d(TAG, "onAudioStateChanged: " + event); mA2dpService.messageFromNative(event); } @VisibleForTesting void onCodecConfigChanged( byte[] address, BluetoothCodecConfig newCodecConfig, BluetoothCodecConfig[] codecsLocalCapabilities, BluetoothCodecConfig[] codecsSelectableCapabilities) { A2dpStackEvent event = new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CODEC_CONFIG_CHANGED); event.device = getDevice(address); event.codecStatus = new BluetoothCodecStatus( newCodecConfig, Arrays.asList(codecsLocalCapabilities), Arrays.asList(codecsSelectableCapabilities)); Log.d(TAG, "onCodecConfigChanged: " + event); mA2dpService.messageFromNative(event); } @VisibleForTesting boolean isMandatoryCodecPreferred(byte[] address) { int enabled = mA2dpService.getOptionalCodecsEnabled(getDevice(address)); Log.d(TAG, "isMandatoryCodecPreferred: optional preference " + enabled); return enabled == BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED; } }
android/app/src/com/android/bluetooth/a2dp/A2dpNativeInterface.java +15 −109 Original line number Diff line number Diff line Loading @@ -21,65 +21,37 @@ */ package com.android.bluetooth.a2dp; import android.bluetooth.BluetoothA2dp; import android.bluetooth.BluetoothAdapter; import static java.util.Objects.requireNonNull; import android.annotation.NonNull; import android.bluetooth.BluetoothCodecConfig; import android.bluetooth.BluetoothCodecStatus; import android.bluetooth.BluetoothCodecType; import android.bluetooth.BluetoothDevice; import android.util.Log; import com.android.bluetooth.Utils; import com.android.bluetooth.btservice.AdapterService; import com.android.bluetooth.flags.Flags; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import java.lang.annotation.Native; import java.util.Arrays; import java.util.List; import java.util.Objects; /** A2DP Native Interface to/from JNI. */ public class A2dpNativeInterface { private static final String TAG = A2dpNativeInterface.class.getSimpleName(); private BluetoothAdapter mAdapter; private AdapterService mAdapterService; @GuardedBy("INSTANCE_LOCK") private static A2dpNativeInterface sInstance; private static BluetoothCodecType[] sSupportedCodecTypes; private static final Object INSTANCE_LOCK = new Object(); @VisibleForTesting private A2dpNativeInterface() { mAdapter = BluetoothAdapter.getDefaultAdapter(); if (mAdapter == null) { Log.wtf(TAG, "No Bluetooth Adapter Available"); } mAdapterService = Objects.requireNonNull( AdapterService.getAdapterService(), "AdapterService cannot be null when A2dpNativeInterface init"); } private final AdapterService mAdapterService; @Native private final A2dpNativeCallback mNativeCallback; /** Get singleton instance. */ public static A2dpNativeInterface getInstance() { synchronized (INSTANCE_LOCK) { if (sInstance == null) { sInstance = new A2dpNativeInterface(); } return sInstance; } } private BluetoothCodecType[] mSupportedCodecTypes; /** Set singleton instance. */ @VisibleForTesting public static void setInstance(A2dpNativeInterface instance) { synchronized (INSTANCE_LOCK) { sInstance = instance; } A2dpNativeInterface( @NonNull AdapterService adapterService, @NonNull A2dpNativeCallback nativeCallback) { mAdapterService = requireNonNull(adapterService); mNativeCallback = requireNonNull(nativeCallback); } /** Loading @@ -103,10 +75,10 @@ public class A2dpNativeInterface { /** Returns the list of locally supported codec types. */ public List<BluetoothCodecType> getSupportedCodecTypes() { if (sSupportedCodecTypes == null) { sSupportedCodecTypes = getSupportedCodecTypesNative(); if (mSupportedCodecTypes == null) { mSupportedCodecTypes = getSupportedCodecTypesNative(); } return Arrays.asList(sSupportedCodecTypes); return Arrays.asList(mSupportedCodecTypes); } /** Loading Loading @@ -161,10 +133,6 @@ public class A2dpNativeInterface { return setCodecConfigPreferenceNative(getByteAddress(device), codecConfigArray); } private BluetoothDevice getDevice(byte[] address) { return mAdapterService.getDeviceFromByte(address); } private byte[] getByteAddress(BluetoothDevice device) { if (device == null) { return Utils.getBytesFromAddress("00:00:00:00:00:00"); Loading @@ -176,68 +144,6 @@ public class A2dpNativeInterface { } } private void sendMessageToService(A2dpStackEvent event) { A2dpService service = A2dpService.getA2dpService(); if (service != null) { service.messageFromNative(event); } else { Log.w(TAG, "Event ignored, service not available: " + event); } } // Callbacks from the native stack back into the Java framework. // All callbacks are routed via the Service which will disambiguate which // state machine the message should be routed to. private void onConnectionStateChanged(byte[] address, int state) { A2dpStackEvent event = new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); event.device = getDevice(address); event.valueInt = state; Log.d(TAG, "onConnectionStateChanged: " + event); sendMessageToService(event); } private void onAudioStateChanged(byte[] address, int state) { A2dpStackEvent event = new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED); event.device = getDevice(address); event.valueInt = state; Log.d(TAG, "onAudioStateChanged: " + event); sendMessageToService(event); } private void onCodecConfigChanged( byte[] address, BluetoothCodecConfig newCodecConfig, BluetoothCodecConfig[] codecsLocalCapabilities, BluetoothCodecConfig[] codecsSelectableCapabilities) { A2dpStackEvent event = new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CODEC_CONFIG_CHANGED); event.device = getDevice(address); event.codecStatus = new BluetoothCodecStatus( newCodecConfig, Arrays.asList(codecsLocalCapabilities), Arrays.asList(codecsSelectableCapabilities)); Log.d(TAG, "onCodecConfigChanged: " + event); sendMessageToService(event); } private boolean isMandatoryCodecPreferred(byte[] address) { A2dpService service = A2dpService.getA2dpService(); if (service != null) { int enabled = service.getOptionalCodecsEnabled(getDevice(address)); Log.d(TAG, "isMandatoryCodecPreferred: optional preference " + enabled); // Optional codecs are more preferred if possible return enabled == BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED; } else { Log.w(TAG, "isMandatoryCodecPreferred: service not available"); return false; } } // Native methods that call into the JNI interface private native void initNative( int maxConnectedAudioDevices, BluetoothCodecConfig[] codecConfigPriorities, Loading
android/app/src/com/android/bluetooth/a2dp/A2dpService.java +14 −4 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import static android.bluetooth.BluetoothProfile.STATE_CONNECTING; import static com.android.bluetooth.Utils.checkCallerTargetSdk; import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNullElseGet; import android.annotation.NonNull; import android.annotation.RequiresPermission; Loading Loading @@ -116,14 +117,20 @@ public class A2dpService extends ProfileService { new AudioManagerAudioDeviceCallback(); public A2dpService(AdapterService adapterService) { this(adapterService, A2dpNativeInterface.getInstance(), Looper.getMainLooper()); this(adapterService, null, Looper.getMainLooper()); } @VisibleForTesting A2dpService(AdapterService adapterService, A2dpNativeInterface nativeInterface, Looper looper) { super(requireNonNull(adapterService)); mAdapterService = adapterService; mNativeInterface = requireNonNull(nativeInterface); mNativeInterface = requireNonNullElseGet( nativeInterface, () -> new A2dpNativeInterface( adapterService, new A2dpNativeCallback(adapterService, this))); mDatabaseManager = requireNonNull(mAdapterService.getDatabase()); mAudioManager = requireNonNull(getSystemService(AudioManager.class)); mCompanionDeviceManager = requireNonNull(getSystemService(CompanionDeviceManager.class)); Loading Loading @@ -879,9 +886,12 @@ public class A2dpService extends ProfileService { // Handle messages from native (JNI) to Java void messageFromNative(A2dpStackEvent stackEvent) { requireNonNull(stackEvent.device); if (!isAvailable()) { Log.w(TAG, "messageFromNative(): service is not available"); return; } BluetoothDevice device = requireNonNull(stackEvent.device); synchronized (mStateMachines) { BluetoothDevice device = stackEvent.device; A2dpStateMachine sm = mStateMachines.get(device); if (sm == null) { if (stackEvent.type == A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED) { Loading
android/app/tests/unit/src/com/android/bluetooth/btservice/ProfileServiceTest.java +2 −5 Original line number Diff line number Diff line Loading @@ -32,7 +32,6 @@ import androidx.test.filters.MediumTest; import androidx.test.runner.AndroidJUnit4; import com.android.bluetooth.TestUtils; import com.android.bluetooth.a2dp.A2dpNativeInterface; import com.android.bluetooth.a2dpsink.A2dpSinkNativeInterface; import com.android.bluetooth.avrcp.AvrcpNativeInterface; import com.android.bluetooth.avrcpcontroller.AvrcpControllerNativeInterface; Loading Loading @@ -80,7 +79,6 @@ public class ProfileServiceTest { private int[] mProfiles; @Mock private A2dpNativeInterface mA2dpNativeInterface; @Mock private A2dpSinkNativeInterface mA2dpSinkNativeInterface; @Mock private AvrcpNativeInterface mAvrcpNativeInterface; @Mock private AvrcpControllerNativeInterface mAvrcpControllerNativeInterface; Loading Loading @@ -155,13 +153,13 @@ public class ProfileServiceTest { profile != BluetoothProfile.HAP_CLIENT && profile != BluetoothProfile.VOLUME_CONTROL && profile != BluetoothProfile.CSIP_SET_COORDINATOR && profile != BluetoothProfile.GATT) && profile != BluetoothProfile.GATT && profile != BluetoothProfile.A2DP) .toArray(); TestUtils.setAdapterService(mAdapterService); Assert.assertNotNull(AdapterService.getAdapterService()); A2dpNativeInterface.setInstance(mA2dpNativeInterface); A2dpSinkNativeInterface.setInstance(mA2dpSinkNativeInterface); AvrcpNativeInterface.setInstance(mAvrcpNativeInterface); AvrcpControllerNativeInterface.setInstance(mAvrcpControllerNativeInterface); Loading @@ -180,7 +178,6 @@ public class ProfileServiceTest { TestUtils.clearAdapterService(mAdapterService); mAdapterService = null; mProfiles = null; A2dpNativeInterface.setInstance(null); A2dpSinkNativeInterface.setInstance(null); AvrcpNativeInterface.setInstance(null); AvrcpControllerNativeInterface.setInstance(null); Loading