Loading android/app/jni/com_android_bluetooth_le_audio.cpp +10 −0 Original line number Diff line number Diff line Loading @@ -41,6 +41,7 @@ using bluetooth::le_audio::LeAudioClientCallbacks; using bluetooth::le_audio::LeAudioClientInterface; namespace android { static jmethodID method_onInitialized; static jmethodID method_onConnectionStateChanged; static jmethodID method_onGroupStatus; static jmethodID method_onGroupNodeStatus; Loading Loading @@ -127,6 +128,14 @@ class LeAudioClientCallbacksImpl : public LeAudioClientCallbacks { public: ~LeAudioClientCallbacksImpl() = default; void OnInitialized(void) override { LOG(INFO) << __func__; std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex); CallbackEnv sCallbackEnv(__func__); if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return; sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onInitialized); } void OnConnectionState(ConnectionState state, const RawAddress& bd_addr) override { LOG(INFO) << __func__ << ", state:" << int(state); Loading Loading @@ -282,6 +291,7 @@ static void classInitNative(JNIEnv* env, jclass clazz) { method_onAudioConf = env->GetMethodID(clazz, "onAudioConf", "(IIIII)V"); method_onSinkAudioLocationAvailable = env->GetMethodID(clazz, "onSinkAudioLocationAvailable", "([BI)V"); method_onInitialized = env->GetMethodID(clazz, "onInitialized", "()V"); method_onConnectionStateChanged = env->GetMethodID(clazz, "onConnectionStateChanged", "(I[B)V"); method_onAudioLocalCodecCapabilities = Loading android/app/src/com/android/bluetooth/le_audio/ContentControlIdKeeper.java +49 −2 Original line number Diff line number Diff line Loading @@ -17,6 +17,15 @@ package com.android.bluetooth.le_audio; import android.bluetooth.BluetoothLeAudio; import android.os.ParcelUuid; import android.util.Pair; import com.android.bluetooth.btservice.ServiceFactory; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.SortedSet; import java.util.TreeSet; Loading @@ -30,8 +39,19 @@ public class ContentControlIdKeeper { public static final int CCID_MAX = 0xFF; private static SortedSet<Integer> sAssignedCcidList = new TreeSet(); private static HashMap<ParcelUuid, Pair<Integer, Integer>> sUserMap = new HashMap(); private static ServiceFactory sServiceFactory = null; public static synchronized int acquireCcid() { /** * Functions is used to acquire Content Control ID (Ccid). Ccid is connected * with a context type and the user uuid. In most of cases user uuid is the GATT service * UUID which makes use of Ccid * * @param userUuid user identifier (GATT service) * @param contextType the context types as defined in {@link BluetoothLeAudio} * @return ccid to be used in the Gatt service Ccid characteristic. */ public static synchronized int acquireCcid(ParcelUuid userUuid, int contextType) { int ccid = CCID_INVALID; if (sAssignedCcidList.size() == 0) { Loading @@ -51,11 +71,38 @@ public class ContentControlIdKeeper { } } if (ccid != CCID_INVALID) sAssignedCcidList.add(ccid); if (ccid != CCID_INVALID) { sAssignedCcidList.add(ccid); sUserMap.put(userUuid, new Pair(ccid, contextType)); if (sServiceFactory == null) { sServiceFactory = new ServiceFactory(); } /* Notify LeAudioService about new ccid */ LeAudioService service = sServiceFactory.getLeAudioService(); if (service != null) { service.setCcidInformation(userUuid, ccid, contextType); } } return ccid; } /** * Release the acquired Ccid * * @param value Ccid value to release */ public static synchronized void releaseCcid(int value) { sAssignedCcidList.remove(value); sUserMap.entrySet().removeIf(entry -> entry.getValue().first.equals(value)); } /** * Get Ccid information. * * @return Map of acquired ccids along with the user information. */ public static synchronized Map<ParcelUuid, Pair<Integer, Integer>> getUserCcidMap() { return Collections.unmodifiableMap(sUserMap); } } android/app/src/com/android/bluetooth/le_audio/LeAudioNativeInterface.java +10 −0 Original line number Diff line number Diff line Loading @@ -90,6 +90,16 @@ public class LeAudioNativeInterface { // 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 onInitialized() { LeAudioStackEvent event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_NATIVE_INITIALIZED); if (DBG) { Log.d(TAG, "onInitialized: " + event); } sendMessageToService(event); } private void onConnectionStateChanged(int state, byte[] address) { LeAudioStackEvent event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); Loading android/app/src/com/android/bluetooth/le_audio/LeAudioService.java +69 −0 Original line number Diff line number Diff line Loading @@ -51,6 +51,7 @@ import android.os.RemoteCallbackList; import android.os.RemoteException; import android.sysprop.BluetoothProperties; import android.util.Log; import android.util.Pair; import com.android.bluetooth.Utils; import com.android.bluetooth.btservice.AdapterService; Loading @@ -58,6 +59,7 @@ import com.android.bluetooth.btservice.ProfileService; import com.android.bluetooth.btservice.ServiceFactory; import com.android.bluetooth.btservice.storage.DatabaseManager; import com.android.bluetooth.mcp.McpService; import com.android.bluetooth.tbs.TbsGatt; import com.android.bluetooth.vc.VolumeControlService; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; Loading Loading @@ -118,6 +120,7 @@ public class LeAudioService extends ProfileService { ServiceFactory mServiceFactory = new ServiceFactory(); LeAudioNativeInterface mLeAudioNativeInterface; boolean mLeAudioNativeIsInitialized = false; LeAudioBroadcasterNativeInterface mLeAudioBroadcasterNativeInterface = null; @VisibleForTesting AudioManager mAudioManager; Loading Loading @@ -327,6 +330,7 @@ public class LeAudioService extends ProfileService { // Cleanup native interfaces mLeAudioNativeInterface.cleanup(); mLeAudioNativeInterface = null; mLeAudioNativeIsInitialized = false; // Set the service and BLE devices as inactive setLeAudioService(null); Loading Loading @@ -552,6 +556,10 @@ public class LeAudioService extends ProfileService { * @return true on success, otherwise false */ boolean groupAddNode(int groupId, BluetoothDevice device) { if (!mLeAudioNativeIsInitialized) { Log.e(TAG, "Le Audio not initialized properly."); return false; } return mLeAudioNativeInterface.groupAddNode(groupId, device); } Loading @@ -562,6 +570,10 @@ public class LeAudioService extends ProfileService { * @return true on success, otherwise false */ boolean groupRemoveNode(int groupId, BluetoothDevice device) { if (!mLeAudioNativeIsInitialized) { Log.e(TAG, "Le Audio not initialized properly."); return false; } return mLeAudioNativeInterface.groupRemoveNode(groupId, device); } Loading Loading @@ -951,6 +963,10 @@ public class LeAudioService extends ProfileService { return; } if (!mLeAudioNativeIsInitialized) { Log.e(TAG, "Le Audio not initialized properly."); return; } mLeAudioNativeInterface.groupSetActive(groupId); } Loading Loading @@ -1336,6 +1352,14 @@ public class LeAudioService extends ProfileService { mBroadcastMetadataList.put(broadcastId, stackEvent.broadcastMetadata); notifyBroadcastMetadataChanged(broadcastId, stackEvent.broadcastMetadata); } } else if (stackEvent.type == LeAudioStackEvent.EVENT_TYPE_NATIVE_INITIALIZED) { mLeAudioNativeIsInitialized = true; for (Map.Entry<ParcelUuid, Pair<Integer, Integer>> entry : ContentControlIdKeeper.getUserCcidMap().entrySet()) { ParcelUuid userUuid = entry.getKey(); Pair<Integer, Integer> ccidInformation = entry.getValue(); setCcidInformation(userUuid, ccidInformation.first, ccidInformation.second); } } if (intent != null) { Loading Loading @@ -1684,6 +1708,25 @@ public class LeAudioService extends ProfileService { } } /** * Set the user application ccid along with used context type * @param userUuid user uuid * @param ccid content control id * @param contextType context type */ public void setCcidInformation(ParcelUuid userUuid, int ccid, int contextType) { /* for the moment we care only for GMCS and GTBS */ if (userUuid != BluetoothUuid.GENERIC_MEDIA_CONTROL && userUuid.getUuid() != TbsGatt.UUID_GTBS) { return; } if (!mLeAudioNativeIsInitialized) { Log.e(TAG, "Le Audio not initialized properly."); return; } mLeAudioNativeInterface.setCcidInformation(ccid, contextType); } /** * Set volume for streaming devices * @param volume volume to set Loading Loading @@ -1977,6 +2020,11 @@ public class LeAudioService extends ProfileService { return; } if (!mLeAudioNativeIsInitialized) { Log.e(TAG, "Le Audio not initialized properly."); return; } mLeAudioNativeInterface.setCodecConfigPreference(groupId, inputCodecConfig, outputCodecConfig); } Loading Loading @@ -2214,6 +2262,27 @@ public class LeAudioService extends ProfileService { } } @Override public void setCcidInformation(ParcelUuid userUuid, int ccid, int contextType, AttributionSource source, SynchronousResultReceiver receiver) { try { Objects.requireNonNull(userUuid, "userUuid cannot be null"); Objects.requireNonNull(source, "source cannot be null"); Objects.requireNonNull(receiver, "receiver cannot be null"); LeAudioService service = getService(source); if (service == null) { throw new IllegalStateException("service is null"); } enforceBluetoothPrivilegedPermission(service); service.setCcidInformation(userUuid, ccid, contextType); receiver.send(null); } catch (RuntimeException e) { receiver.propagateException(e); } } @Override public void getGroupId(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver) { Loading android/app/src/com/android/bluetooth/le_audio/LeAudioStackEvent.java +4 −1 Original line number Diff line number Diff line Loading @@ -36,8 +36,9 @@ public class LeAudioStackEvent { public static final int EVENT_TYPE_SINK_AUDIO_LOCATION_AVAILABLE = 5; public static final int EVENT_TYPE_AUDIO_LOCAL_CODEC_CONFIG_CAPA_CHANGED = 6; public static final int EVENT_TYPE_AUDIO_GROUP_CODEC_CONFIG_CHANGED = 7; public static final int EVENT_TYPE_NATIVE_INITIALIZED = 8; // -------- DO NOT PUT ANY NEW UNICAST EVENTS BELOW THIS LINE------------- public static final int EVENT_TYPE_UNICAST_MAX = 8; public static final int EVENT_TYPE_UNICAST_MAX = 9; // Broadcast related events public static final int EVENT_TYPE_BROADCAST_CREATED = EVENT_TYPE_UNICAST_MAX + 1; Loading Loading @@ -136,6 +137,8 @@ public class LeAudioStackEvent { return "EVENT_TYPE_AUDIO_LOCAL_CODEC_CONFIG_CAPA_CHANGED"; case EVENT_TYPE_AUDIO_GROUP_CODEC_CONFIG_CHANGED: return "EVENT_TYPE_AUDIO_GROUP_CODEC_CONFIG_CHANGED"; case EVENT_TYPE_NATIVE_INITIALIZED: return "EVENT_TYPE_NATIVE_INITIALIZED"; default: return "EVENT_TYPE_UNKNOWN:" + type; } Loading Loading
android/app/jni/com_android_bluetooth_le_audio.cpp +10 −0 Original line number Diff line number Diff line Loading @@ -41,6 +41,7 @@ using bluetooth::le_audio::LeAudioClientCallbacks; using bluetooth::le_audio::LeAudioClientInterface; namespace android { static jmethodID method_onInitialized; static jmethodID method_onConnectionStateChanged; static jmethodID method_onGroupStatus; static jmethodID method_onGroupNodeStatus; Loading Loading @@ -127,6 +128,14 @@ class LeAudioClientCallbacksImpl : public LeAudioClientCallbacks { public: ~LeAudioClientCallbacksImpl() = default; void OnInitialized(void) override { LOG(INFO) << __func__; std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex); CallbackEnv sCallbackEnv(__func__); if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return; sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onInitialized); } void OnConnectionState(ConnectionState state, const RawAddress& bd_addr) override { LOG(INFO) << __func__ << ", state:" << int(state); Loading Loading @@ -282,6 +291,7 @@ static void classInitNative(JNIEnv* env, jclass clazz) { method_onAudioConf = env->GetMethodID(clazz, "onAudioConf", "(IIIII)V"); method_onSinkAudioLocationAvailable = env->GetMethodID(clazz, "onSinkAudioLocationAvailable", "([BI)V"); method_onInitialized = env->GetMethodID(clazz, "onInitialized", "()V"); method_onConnectionStateChanged = env->GetMethodID(clazz, "onConnectionStateChanged", "(I[B)V"); method_onAudioLocalCodecCapabilities = Loading
android/app/src/com/android/bluetooth/le_audio/ContentControlIdKeeper.java +49 −2 Original line number Diff line number Diff line Loading @@ -17,6 +17,15 @@ package com.android.bluetooth.le_audio; import android.bluetooth.BluetoothLeAudio; import android.os.ParcelUuid; import android.util.Pair; import com.android.bluetooth.btservice.ServiceFactory; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.SortedSet; import java.util.TreeSet; Loading @@ -30,8 +39,19 @@ public class ContentControlIdKeeper { public static final int CCID_MAX = 0xFF; private static SortedSet<Integer> sAssignedCcidList = new TreeSet(); private static HashMap<ParcelUuid, Pair<Integer, Integer>> sUserMap = new HashMap(); private static ServiceFactory sServiceFactory = null; public static synchronized int acquireCcid() { /** * Functions is used to acquire Content Control ID (Ccid). Ccid is connected * with a context type and the user uuid. In most of cases user uuid is the GATT service * UUID which makes use of Ccid * * @param userUuid user identifier (GATT service) * @param contextType the context types as defined in {@link BluetoothLeAudio} * @return ccid to be used in the Gatt service Ccid characteristic. */ public static synchronized int acquireCcid(ParcelUuid userUuid, int contextType) { int ccid = CCID_INVALID; if (sAssignedCcidList.size() == 0) { Loading @@ -51,11 +71,38 @@ public class ContentControlIdKeeper { } } if (ccid != CCID_INVALID) sAssignedCcidList.add(ccid); if (ccid != CCID_INVALID) { sAssignedCcidList.add(ccid); sUserMap.put(userUuid, new Pair(ccid, contextType)); if (sServiceFactory == null) { sServiceFactory = new ServiceFactory(); } /* Notify LeAudioService about new ccid */ LeAudioService service = sServiceFactory.getLeAudioService(); if (service != null) { service.setCcidInformation(userUuid, ccid, contextType); } } return ccid; } /** * Release the acquired Ccid * * @param value Ccid value to release */ public static synchronized void releaseCcid(int value) { sAssignedCcidList.remove(value); sUserMap.entrySet().removeIf(entry -> entry.getValue().first.equals(value)); } /** * Get Ccid information. * * @return Map of acquired ccids along with the user information. */ public static synchronized Map<ParcelUuid, Pair<Integer, Integer>> getUserCcidMap() { return Collections.unmodifiableMap(sUserMap); } }
android/app/src/com/android/bluetooth/le_audio/LeAudioNativeInterface.java +10 −0 Original line number Diff line number Diff line Loading @@ -90,6 +90,16 @@ public class LeAudioNativeInterface { // 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 onInitialized() { LeAudioStackEvent event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_NATIVE_INITIALIZED); if (DBG) { Log.d(TAG, "onInitialized: " + event); } sendMessageToService(event); } private void onConnectionStateChanged(int state, byte[] address) { LeAudioStackEvent event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); Loading
android/app/src/com/android/bluetooth/le_audio/LeAudioService.java +69 −0 Original line number Diff line number Diff line Loading @@ -51,6 +51,7 @@ import android.os.RemoteCallbackList; import android.os.RemoteException; import android.sysprop.BluetoothProperties; import android.util.Log; import android.util.Pair; import com.android.bluetooth.Utils; import com.android.bluetooth.btservice.AdapterService; Loading @@ -58,6 +59,7 @@ import com.android.bluetooth.btservice.ProfileService; import com.android.bluetooth.btservice.ServiceFactory; import com.android.bluetooth.btservice.storage.DatabaseManager; import com.android.bluetooth.mcp.McpService; import com.android.bluetooth.tbs.TbsGatt; import com.android.bluetooth.vc.VolumeControlService; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; Loading Loading @@ -118,6 +120,7 @@ public class LeAudioService extends ProfileService { ServiceFactory mServiceFactory = new ServiceFactory(); LeAudioNativeInterface mLeAudioNativeInterface; boolean mLeAudioNativeIsInitialized = false; LeAudioBroadcasterNativeInterface mLeAudioBroadcasterNativeInterface = null; @VisibleForTesting AudioManager mAudioManager; Loading Loading @@ -327,6 +330,7 @@ public class LeAudioService extends ProfileService { // Cleanup native interfaces mLeAudioNativeInterface.cleanup(); mLeAudioNativeInterface = null; mLeAudioNativeIsInitialized = false; // Set the service and BLE devices as inactive setLeAudioService(null); Loading Loading @@ -552,6 +556,10 @@ public class LeAudioService extends ProfileService { * @return true on success, otherwise false */ boolean groupAddNode(int groupId, BluetoothDevice device) { if (!mLeAudioNativeIsInitialized) { Log.e(TAG, "Le Audio not initialized properly."); return false; } return mLeAudioNativeInterface.groupAddNode(groupId, device); } Loading @@ -562,6 +570,10 @@ public class LeAudioService extends ProfileService { * @return true on success, otherwise false */ boolean groupRemoveNode(int groupId, BluetoothDevice device) { if (!mLeAudioNativeIsInitialized) { Log.e(TAG, "Le Audio not initialized properly."); return false; } return mLeAudioNativeInterface.groupRemoveNode(groupId, device); } Loading Loading @@ -951,6 +963,10 @@ public class LeAudioService extends ProfileService { return; } if (!mLeAudioNativeIsInitialized) { Log.e(TAG, "Le Audio not initialized properly."); return; } mLeAudioNativeInterface.groupSetActive(groupId); } Loading Loading @@ -1336,6 +1352,14 @@ public class LeAudioService extends ProfileService { mBroadcastMetadataList.put(broadcastId, stackEvent.broadcastMetadata); notifyBroadcastMetadataChanged(broadcastId, stackEvent.broadcastMetadata); } } else if (stackEvent.type == LeAudioStackEvent.EVENT_TYPE_NATIVE_INITIALIZED) { mLeAudioNativeIsInitialized = true; for (Map.Entry<ParcelUuid, Pair<Integer, Integer>> entry : ContentControlIdKeeper.getUserCcidMap().entrySet()) { ParcelUuid userUuid = entry.getKey(); Pair<Integer, Integer> ccidInformation = entry.getValue(); setCcidInformation(userUuid, ccidInformation.first, ccidInformation.second); } } if (intent != null) { Loading Loading @@ -1684,6 +1708,25 @@ public class LeAudioService extends ProfileService { } } /** * Set the user application ccid along with used context type * @param userUuid user uuid * @param ccid content control id * @param contextType context type */ public void setCcidInformation(ParcelUuid userUuid, int ccid, int contextType) { /* for the moment we care only for GMCS and GTBS */ if (userUuid != BluetoothUuid.GENERIC_MEDIA_CONTROL && userUuid.getUuid() != TbsGatt.UUID_GTBS) { return; } if (!mLeAudioNativeIsInitialized) { Log.e(TAG, "Le Audio not initialized properly."); return; } mLeAudioNativeInterface.setCcidInformation(ccid, contextType); } /** * Set volume for streaming devices * @param volume volume to set Loading Loading @@ -1977,6 +2020,11 @@ public class LeAudioService extends ProfileService { return; } if (!mLeAudioNativeIsInitialized) { Log.e(TAG, "Le Audio not initialized properly."); return; } mLeAudioNativeInterface.setCodecConfigPreference(groupId, inputCodecConfig, outputCodecConfig); } Loading Loading @@ -2214,6 +2262,27 @@ public class LeAudioService extends ProfileService { } } @Override public void setCcidInformation(ParcelUuid userUuid, int ccid, int contextType, AttributionSource source, SynchronousResultReceiver receiver) { try { Objects.requireNonNull(userUuid, "userUuid cannot be null"); Objects.requireNonNull(source, "source cannot be null"); Objects.requireNonNull(receiver, "receiver cannot be null"); LeAudioService service = getService(source); if (service == null) { throw new IllegalStateException("service is null"); } enforceBluetoothPrivilegedPermission(service); service.setCcidInformation(userUuid, ccid, contextType); receiver.send(null); } catch (RuntimeException e) { receiver.propagateException(e); } } @Override public void getGroupId(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver) { Loading
android/app/src/com/android/bluetooth/le_audio/LeAudioStackEvent.java +4 −1 Original line number Diff line number Diff line Loading @@ -36,8 +36,9 @@ public class LeAudioStackEvent { public static final int EVENT_TYPE_SINK_AUDIO_LOCATION_AVAILABLE = 5; public static final int EVENT_TYPE_AUDIO_LOCAL_CODEC_CONFIG_CAPA_CHANGED = 6; public static final int EVENT_TYPE_AUDIO_GROUP_CODEC_CONFIG_CHANGED = 7; public static final int EVENT_TYPE_NATIVE_INITIALIZED = 8; // -------- DO NOT PUT ANY NEW UNICAST EVENTS BELOW THIS LINE------------- public static final int EVENT_TYPE_UNICAST_MAX = 8; public static final int EVENT_TYPE_UNICAST_MAX = 9; // Broadcast related events public static final int EVENT_TYPE_BROADCAST_CREATED = EVENT_TYPE_UNICAST_MAX + 1; Loading Loading @@ -136,6 +137,8 @@ public class LeAudioStackEvent { return "EVENT_TYPE_AUDIO_LOCAL_CODEC_CONFIG_CAPA_CHANGED"; case EVENT_TYPE_AUDIO_GROUP_CODEC_CONFIG_CHANGED: return "EVENT_TYPE_AUDIO_GROUP_CODEC_CONFIG_CHANGED"; case EVENT_TYPE_NATIVE_INITIALIZED: return "EVENT_TYPE_NATIVE_INITIALIZED"; default: return "EVENT_TYPE_UNKNOWN:" + type; } Loading