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.util.Log; 28 29 import com.android.bluetooth.Utils; 30 import com.android.internal.annotations.GuardedBy; 31 32 /** 33 * LeAudio Native Interface to/from JNI. 34 */ 35 public class LeAudioNativeInterface { 36 private static final String TAG = "LeAudioNativeInterface"; 37 private static final boolean DBG = true; 38 private BluetoothAdapter mAdapter; 39 40 @GuardedBy("INSTANCE_LOCK") 41 private static LeAudioNativeInterface sInstance; 42 private static final Object INSTANCE_LOCK = new Object(); 43 44 static { classInitNative()45 classInitNative(); 46 } 47 LeAudioNativeInterface()48 private LeAudioNativeInterface() { 49 mAdapter = BluetoothAdapter.getDefaultAdapter(); 50 if (mAdapter == null) { 51 Log.wtfStack(TAG, "No Bluetooth Adapter Available"); 52 } 53 } 54 55 /** 56 * Get singleton instance. 57 */ getInstance()58 public static LeAudioNativeInterface getInstance() { 59 synchronized (INSTANCE_LOCK) { 60 if (sInstance == null) { 61 sInstance = new LeAudioNativeInterface(); 62 } 63 return sInstance; 64 } 65 } 66 getByteAddress(BluetoothDevice device)67 private byte[] getByteAddress(BluetoothDevice device) { 68 if (device == null) { 69 return Utils.getBytesFromAddress("00:00:00:00:00:00"); 70 } 71 return Utils.getBytesFromAddress(device.getAddress()); 72 } 73 sendMessageToService(LeAudioStackEvent event)74 private void sendMessageToService(LeAudioStackEvent event) { 75 LeAudioService service = LeAudioService.getLeAudioService(); 76 if (service != null) { 77 service.messageFromNative(event); 78 } else { 79 Log.e(TAG, "Event ignored, service not available: " + event); 80 } 81 } 82 getDevice(byte[] address)83 private BluetoothDevice getDevice(byte[] address) { 84 return mAdapter.getRemoteDevice(address); 85 } 86 87 // Callbacks from the native stack back into the Java framework. 88 // All callbacks are routed via the Service which will disambiguate which 89 // state machine the message should be routed to. onConnectionStateChanged(int state, byte[] address)90 private void onConnectionStateChanged(int state, byte[] address) { 91 LeAudioStackEvent event = 92 new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); 93 event.device = getDevice(address); 94 event.valueInt1 = state; 95 96 if (DBG) { 97 Log.d(TAG, "onConnectionStateChanged: " + event); 98 } 99 sendMessageToService(event); 100 } 101 102 // Callbacks from the native stack back into the Java framework. 103 // All callbacks are routed via the Service which will disambiguate which 104 // state machine the message should be routed to. onSetMemberAvailable(byte[] address, int groupId)105 private void onSetMemberAvailable(byte[] address, int groupId) { 106 LeAudioStackEvent event = 107 new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_SET_MEMBER_AVAILABLE); 108 event.device = getDevice(address); 109 event.valueInt1 = groupId; 110 if (DBG) { 111 Log.d(TAG, "onSetMemberAvailable: " + event); 112 } 113 sendMessageToService(event); 114 } 115 onGroupStatus(int groupId, int groupStatus, int groupFlags)116 private void onGroupStatus(int groupId, int groupStatus, int groupFlags) { 117 LeAudioStackEvent event = 118 new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_GROUP_STATUS_CHANGED); 119 event.valueInt1 = groupId; 120 event.valueInt2 = groupStatus; 121 event.valueInt3 = groupFlags; 122 event.device = null; 123 124 if (DBG) { 125 Log.d(TAG, "onGroupStatus: " + event); 126 } 127 sendMessageToService(event); 128 } 129 onAudioConf(int direction, int groupId, int sinkAudioLocation, int sourceAudioLocation, byte[] address)130 private void onAudioConf(int direction, int groupId, int sinkAudioLocation, 131 int sourceAudioLocation, byte[] address) { 132 LeAudioStackEvent event = 133 new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_AUDIO_CONF_CHANGED); 134 event.valueInt1 = direction; 135 event.valueInt2 = groupId; 136 event.valueInt3 = sinkAudioLocation; 137 event.valueInt4 = sourceAudioLocation; 138 event.device = getDevice(address); 139 140 if (DBG) { 141 Log.d(TAG, "onAudioConf: " + event); 142 } 143 sendMessageToService(event); 144 } 145 146 /** 147 * Initializes the native interface. 148 * 149 * priorities to configure. 150 */ init()151 public void init() { 152 initNative(); 153 } 154 155 /** 156 * Cleanup the native interface. 157 */ cleanup()158 public void cleanup() { 159 cleanupNative(); 160 } 161 162 /** 163 * Initiates LeAudio connection to a remote device. 164 * 165 * @param device the remote device 166 * @return true on success, otherwise false. 167 */ connectLeAudio(BluetoothDevice device)168 public boolean connectLeAudio(BluetoothDevice device) { 169 return connectLeAudioNative(getByteAddress(device)); 170 } 171 172 /** 173 * Disconnects LeAudio from a remote device. 174 * 175 * @param device the remote device 176 * @return true on success, otherwise false. 177 */ disconnectLeAudio(BluetoothDevice device)178 public boolean disconnectLeAudio(BluetoothDevice device) { 179 return disconnectLeAudioNative(getByteAddress(device)); 180 } 181 182 /** 183 * Enable content streaming. 184 * @param groupId group identifier 185 * @param contentType type of content to stream 186 */ groupStream(int groupId, int contentType)187 public void groupStream(int groupId, int contentType) { 188 groupStreamNative(groupId, contentType); 189 } 190 191 /** 192 * Suspend content streaming. 193 * @param groupId group identifier 194 */ groupSuspend(int groupId)195 public void groupSuspend(int groupId) { 196 groupSuspendNative(groupId); 197 } 198 199 /** 200 * Stop all content streaming. 201 * @param groupId group identifier 202 * TODO: Maybe we should use also pass the content type argument 203 */ groupStop(int groupId)204 public void groupStop(int groupId) { 205 groupStopNative(groupId); 206 } 207 208 // Native methods that call into the JNI interface classInitNative()209 private static native void classInitNative(); initNative()210 private native void initNative(); cleanupNative()211 private native void cleanupNative(); connectLeAudioNative(byte[] address)212 private native boolean connectLeAudioNative(byte[] address); disconnectLeAudioNative(byte[] address)213 private native boolean disconnectLeAudioNative(byte[] address); groupStreamNative(int groupId, int contentType)214 private native void groupStreamNative(int groupId, int contentType); groupSuspendNative(int groupId)215 private native void groupSuspendNative(int groupId); groupStopNative(int groupId)216 private native void groupStopNative(int groupId); 217 } 218