1 /* 2 * Copyright 2020 HIMSA II K/S - www.himsa.com. 3 * Represented by EHIMA - www.ehima.com 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 /* 19 * Defines the native interface that is used by state machine/service to 20 * send or receive messages from the native stack. This file is registered 21 * for the native methods in the corresponding JNI C++ file. 22 */ 23 package com.android.bluetooth.le_audio; 24 25 import android.bluetooth.BluetoothAdapter; 26 import android.bluetooth.BluetoothDevice; 27 import android.bluetooth.BluetoothLeAudioCodecConfig; 28 import android.util.Log; 29 30 import com.android.bluetooth.Utils; 31 import com.android.internal.annotations.GuardedBy; 32 import com.android.internal.annotations.VisibleForTesting; 33 34 import java.util.Arrays; 35 36 /** 37 * LeAudio Native Interface to/from JNI. 38 */ 39 public class LeAudioNativeInterface { 40 private static final String TAG = "LeAudioNativeInterface"; 41 private static final boolean DBG = true; 42 private BluetoothAdapter mAdapter; 43 44 @GuardedBy("INSTANCE_LOCK") 45 private static LeAudioNativeInterface sInstance; 46 private static final Object INSTANCE_LOCK = new Object(); 47 48 static { classInitNative()49 classInitNative(); 50 } 51 LeAudioNativeInterface()52 private LeAudioNativeInterface() { 53 mAdapter = BluetoothAdapter.getDefaultAdapter(); 54 if (mAdapter == null) { 55 Log.wtf(TAG, "No Bluetooth Adapter Available"); 56 } 57 } 58 59 /** 60 * Get singleton instance. 61 */ getInstance()62 public static LeAudioNativeInterface getInstance() { 63 synchronized (INSTANCE_LOCK) { 64 if (sInstance == null) { 65 sInstance = new LeAudioNativeInterface(); 66 } 67 return sInstance; 68 } 69 } 70 71 /** 72 * Set singleton instance. 73 */ 74 @VisibleForTesting setInstance(LeAudioNativeInterface instance)75 static void setInstance(LeAudioNativeInterface instance) { 76 synchronized (INSTANCE_LOCK) { 77 sInstance = instance; 78 } 79 } 80 getByteAddress(BluetoothDevice device)81 private byte[] getByteAddress(BluetoothDevice device) { 82 if (device == null) { 83 return Utils.getBytesFromAddress("00:00:00:00:00:00"); 84 } 85 return Utils.getBytesFromAddress(device.getAddress()); 86 } 87 sendMessageToService(LeAudioStackEvent event)88 private void sendMessageToService(LeAudioStackEvent event) { 89 LeAudioService service = LeAudioService.getLeAudioService(); 90 if (service != null) { 91 service.messageFromNative(event); 92 } else { 93 Log.e(TAG, "Event ignored, service not available: " + event); 94 } 95 } 96 getDevice(byte[] address)97 private BluetoothDevice getDevice(byte[] address) { 98 return mAdapter.getRemoteDevice(address); 99 } 100 101 // Callbacks from the native stack back into the Java framework. 102 // All callbacks are routed via the Service which will disambiguate which 103 // state machine the message should be routed to. onInitialized()104 private void onInitialized() { 105 LeAudioStackEvent event = 106 new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_NATIVE_INITIALIZED); 107 108 if (DBG) { 109 Log.d(TAG, "onInitialized: " + event); 110 } 111 sendMessageToService(event); 112 } 113 114 @VisibleForTesting onConnectionStateChanged(int state, byte[] address)115 void onConnectionStateChanged(int state, byte[] address) { 116 LeAudioStackEvent event = 117 new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); 118 event.device = getDevice(address); 119 event.valueInt1 = state; 120 121 if (DBG) { 122 Log.d(TAG, "onConnectionStateChanged: " + event); 123 } 124 sendMessageToService(event); 125 } 126 127 @VisibleForTesting onGroupStatus(int groupId, int groupStatus)128 void onGroupStatus(int groupId, int groupStatus) { 129 LeAudioStackEvent event = 130 new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_GROUP_STATUS_CHANGED); 131 event.valueInt1 = groupId; 132 event.valueInt2 = groupStatus; 133 134 if (DBG) { 135 Log.d(TAG, "onGroupStatus: " + event); 136 } 137 sendMessageToService(event); 138 } 139 140 @VisibleForTesting onGroupNodeStatus(byte[] address, int groupId, int nodeStatus)141 void onGroupNodeStatus(byte[] address, int groupId, int nodeStatus) { 142 LeAudioStackEvent event = 143 new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_GROUP_NODE_STATUS_CHANGED); 144 event.valueInt1 = groupId; 145 event.valueInt2 = nodeStatus; 146 event.device = getDevice(address); 147 148 if (DBG) { 149 Log.d(TAG, "onGroupNodeStatus: " + event); 150 } 151 sendMessageToService(event); 152 } 153 154 @VisibleForTesting onAudioConf(int direction, int groupId, int sinkAudioLocation, int sourceAudioLocation, int availableContexts)155 void onAudioConf(int direction, int groupId, int sinkAudioLocation, 156 int sourceAudioLocation, int availableContexts) { 157 LeAudioStackEvent event = 158 new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_AUDIO_CONF_CHANGED); 159 event.valueInt1 = direction; 160 event.valueInt2 = groupId; 161 event.valueInt3 = sinkAudioLocation; 162 event.valueInt4 = sourceAudioLocation; 163 event.valueInt5 = availableContexts; 164 165 if (DBG) { 166 Log.d(TAG, "onAudioConf: " + event); 167 } 168 sendMessageToService(event); 169 } 170 171 @VisibleForTesting onSinkAudioLocationAvailable(byte[] address, int sinkAudioLocation)172 void onSinkAudioLocationAvailable(byte[] address, int sinkAudioLocation) { 173 LeAudioStackEvent event = 174 new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_SINK_AUDIO_LOCATION_AVAILABLE); 175 event.device = getDevice(address); 176 event.valueInt1 = sinkAudioLocation; 177 178 if (DBG) { 179 Log.d(TAG, "onSinkAudioLocationAvailable: " + event); 180 } 181 sendMessageToService(event); 182 } 183 184 @VisibleForTesting onAudioLocalCodecCapabilities( BluetoothLeAudioCodecConfig[] localInputCodecCapabilities, BluetoothLeAudioCodecConfig[] localOutputCodecCapabilities)185 void onAudioLocalCodecCapabilities( 186 BluetoothLeAudioCodecConfig[] localInputCodecCapabilities, 187 BluetoothLeAudioCodecConfig[] localOutputCodecCapabilities) { 188 LeAudioStackEvent event = 189 new LeAudioStackEvent( 190 LeAudioStackEvent.EVENT_TYPE_AUDIO_LOCAL_CODEC_CONFIG_CAPA_CHANGED); 191 192 event.valueCodecList1 = Arrays.asList(localInputCodecCapabilities); 193 event.valueCodecList2 = Arrays.asList(localOutputCodecCapabilities); 194 195 if (DBG) { 196 Log.d(TAG, "onAudioLocalCodecCapabilities: " + event); 197 } 198 sendMessageToService(event); 199 } 200 201 @VisibleForTesting onAudioGroupCodecConf(int groupId, BluetoothLeAudioCodecConfig inputCodecConfig, BluetoothLeAudioCodecConfig outputCodecConfig, BluetoothLeAudioCodecConfig [] inputSelectableCodecConfig, BluetoothLeAudioCodecConfig [] outputSelectableCodecConfig)202 void onAudioGroupCodecConf(int groupId, BluetoothLeAudioCodecConfig inputCodecConfig, 203 BluetoothLeAudioCodecConfig outputCodecConfig, 204 BluetoothLeAudioCodecConfig [] inputSelectableCodecConfig, 205 BluetoothLeAudioCodecConfig [] outputSelectableCodecConfig) { 206 LeAudioStackEvent event = 207 new LeAudioStackEvent( 208 LeAudioStackEvent.EVENT_TYPE_AUDIO_GROUP_CODEC_CONFIG_CHANGED); 209 210 event.valueInt1 = groupId; 211 event.valueCodec1 = inputCodecConfig; 212 event.valueCodec2 = outputCodecConfig; 213 event.valueCodecList1 = Arrays.asList(inputSelectableCodecConfig); 214 event.valueCodecList2 = Arrays.asList(outputSelectableCodecConfig); 215 216 if (DBG) { 217 Log.d(TAG, "onAudioGroupCodecConf: " + event); 218 } 219 sendMessageToService(event); 220 } 221 222 /** 223 * Initializes the native interface. 224 * 225 * priorities to configure. 226 */ init(BluetoothLeAudioCodecConfig[] codecConfigOffloading)227 public void init(BluetoothLeAudioCodecConfig[] codecConfigOffloading) { 228 initNative(codecConfigOffloading); 229 } 230 231 /** 232 * Cleanup the native interface. 233 */ cleanup()234 public void cleanup() { 235 cleanupNative(); 236 } 237 238 /** 239 * Initiates LeAudio connection to a remote device. 240 * 241 * @param device the remote device 242 * @return true on success, otherwise false. 243 */ connectLeAudio(BluetoothDevice device)244 public boolean connectLeAudio(BluetoothDevice device) { 245 return connectLeAudioNative(getByteAddress(device)); 246 } 247 248 /** 249 * Disconnects LeAudio from a remote device. 250 * 251 * @param device the remote device 252 * @return true on success, otherwise false. 253 */ disconnectLeAudio(BluetoothDevice device)254 public boolean disconnectLeAudio(BluetoothDevice device) { 255 return disconnectLeAudioNative(getByteAddress(device)); 256 } 257 258 /** 259 * Enable/Disable LeAudio for the group. 260 * 261 * @param device the remote device 262 * @param enabled true if enabled, false to disabled 263 * @return true on success, otherwise false. 264 */ setEnableState(BluetoothDevice device, boolean enabled)265 public boolean setEnableState(BluetoothDevice device, boolean enabled) { 266 return setEnableStateNative(getByteAddress(device), enabled); 267 } 268 269 /** 270 * Add new Node into a group. 271 * @param groupId group identifier 272 * @param device remote device 273 */ groupAddNode(int groupId, BluetoothDevice device)274 public boolean groupAddNode(int groupId, BluetoothDevice device) { 275 return groupAddNodeNative(groupId, getByteAddress(device)); 276 } 277 278 /** 279 * Add new Node into a group. 280 * @param groupId group identifier 281 * @param device remote device 282 */ groupRemoveNode(int groupId, BluetoothDevice device)283 public boolean groupRemoveNode(int groupId, BluetoothDevice device) { 284 return groupRemoveNodeNative(groupId, getByteAddress(device)); 285 } 286 287 /** 288 * Set active group. 289 * @param groupId group ID to set as active 290 */ groupSetActive(int groupId)291 public void groupSetActive(int groupId) { 292 groupSetActiveNative(groupId); 293 } 294 295 /** 296 * Set codec config preference. 297 * @param groupId group ID for the preference 298 * @param inputCodecConfig input codec configuration 299 * @param outputCodecConfig output codec configuration 300 */ setCodecConfigPreference(int groupId, BluetoothLeAudioCodecConfig inputCodecConfig, BluetoothLeAudioCodecConfig outputCodecConfig)301 public void setCodecConfigPreference(int groupId, 302 BluetoothLeAudioCodecConfig inputCodecConfig, 303 BluetoothLeAudioCodecConfig outputCodecConfig) { 304 setCodecConfigPreferenceNative(groupId, inputCodecConfig, outputCodecConfig); 305 } 306 307 /** 308 * Set content control id (Ccid) along with context type. 309 * @param ccid content control id 310 * @param contextType assigned contextType 311 */ setCcidInformation(int ccid, int contextType)312 public void setCcidInformation(int ccid, int contextType) { 313 if (DBG) { 314 Log.d(TAG, "setCcidInformation ccid: " + ccid + " context type: " + contextType); 315 } 316 setCcidInformationNative(ccid, contextType); 317 } 318 319 /** 320 * Set in call call flag. 321 * @param inCall true when device in call (any state), false otherwise 322 */ setInCall(boolean inCall)323 public void setInCall(boolean inCall) { 324 if (DBG) { 325 Log.d(TAG, "setInCall inCall: " + inCall); 326 } 327 setInCallNative(inCall); 328 } 329 330 /** 331 * Sends the audio preferences for the groupId to the native stack. 332 * 333 * @param groupId is the groupId corresponding to the preferences 334 * @param isOutputPreferenceLeAudio whether LEA is preferred for OUTPUT_ONLY 335 * @param isDuplexPreferenceLeAudio whether LEA is preferred for DUPLEX 336 */ sendAudioProfilePreferences(int groupId, boolean isOutputPreferenceLeAudio, boolean isDuplexPreferenceLeAudio)337 public void sendAudioProfilePreferences(int groupId, boolean isOutputPreferenceLeAudio, 338 boolean isDuplexPreferenceLeAudio) { 339 if (DBG) { 340 Log.d(TAG, "sendAudioProfilePreferences groupId=" + groupId 341 + ", isOutputPreferenceLeAudio=" + isOutputPreferenceLeAudio 342 + ", isDuplexPreferenceLeAudio=" + isDuplexPreferenceLeAudio); 343 } 344 sendAudioProfilePreferencesNative(groupId, isOutputPreferenceLeAudio, 345 isDuplexPreferenceLeAudio); 346 } 347 348 // Native methods that call into the JNI interface classInitNative()349 private static native void classInitNative(); initNative(BluetoothLeAudioCodecConfig[] codecConfigOffloading)350 private native void initNative(BluetoothLeAudioCodecConfig[] codecConfigOffloading); cleanupNative()351 private native void cleanupNative(); connectLeAudioNative(byte[] address)352 private native boolean connectLeAudioNative(byte[] address); disconnectLeAudioNative(byte[] address)353 private native boolean disconnectLeAudioNative(byte[] address); setEnableStateNative(byte[] address, boolean enabled)354 private native boolean setEnableStateNative(byte[] address, boolean enabled); groupAddNodeNative(int groupId, byte[] address)355 private native boolean groupAddNodeNative(int groupId, byte[] address); groupRemoveNodeNative(int groupId, byte[] address)356 private native boolean groupRemoveNodeNative(int groupId, byte[] address); groupSetActiveNative(int groupId)357 private native void groupSetActiveNative(int groupId); setCodecConfigPreferenceNative(int groupId, BluetoothLeAudioCodecConfig inputCodecConfig, BluetoothLeAudioCodecConfig outputCodecConfig)358 private native void setCodecConfigPreferenceNative(int groupId, 359 BluetoothLeAudioCodecConfig inputCodecConfig, 360 BluetoothLeAudioCodecConfig outputCodecConfig); setCcidInformationNative(int ccid, int contextType)361 private native void setCcidInformationNative(int ccid, int contextType); setInCallNative(boolean inCall)362 private native void setInCallNative(boolean inCall); 363 /*package*/ sendAudioProfilePreferencesNative(int groupId, boolean isOutputPreferenceLeAudio, boolean isDuplexPreferenceLeAudio)364 private native void sendAudioProfilePreferencesNative(int groupId, 365 boolean isOutputPreferenceLeAudio, boolean isDuplexPreferenceLeAudio); 366 } 367