1 /* 2 * Copyright 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 /* 18 * Defines the native inteface that is used by state machine/service to 19 * send or receive messages from the native stack. This file is registered 20 * for the native methods in the corresponding JNI C++ file. 21 */ 22 package com.android.bluetooth.a2dp; 23 24 import android.bluetooth.BluetoothA2dp; 25 import android.bluetooth.BluetoothAdapter; 26 import android.bluetooth.BluetoothCodecConfig; 27 import android.bluetooth.BluetoothCodecStatus; 28 import android.bluetooth.BluetoothCodecType; 29 import android.bluetooth.BluetoothDevice; 30 import android.util.Log; 31 32 import com.android.bluetooth.Utils; 33 import com.android.bluetooth.btservice.AdapterService; 34 import com.android.bluetooth.flags.Flags; 35 import com.android.internal.annotations.GuardedBy; 36 import com.android.internal.annotations.VisibleForTesting; 37 38 import java.util.Arrays; 39 import java.util.List; 40 import java.util.Objects; 41 42 /** A2DP Native Interface to/from JNI. */ 43 public class A2dpNativeInterface { 44 private static final String TAG = A2dpNativeInterface.class.getSimpleName(); 45 private BluetoothAdapter mAdapter; 46 private AdapterService mAdapterService; 47 48 @GuardedBy("INSTANCE_LOCK") 49 private static A2dpNativeInterface sInstance; 50 51 private static BluetoothCodecType[] sSupportedCodecTypes; 52 53 private static final Object INSTANCE_LOCK = new Object(); 54 55 @VisibleForTesting A2dpNativeInterface()56 private A2dpNativeInterface() { 57 mAdapter = BluetoothAdapter.getDefaultAdapter(); 58 if (mAdapter == null) { 59 Log.wtf(TAG, "No Bluetooth Adapter Available"); 60 } 61 mAdapterService = 62 Objects.requireNonNull( 63 AdapterService.getAdapterService(), 64 "AdapterService cannot be null when A2dpNativeInterface init"); 65 } 66 67 /** Get singleton instance. */ getInstance()68 public static A2dpNativeInterface getInstance() { 69 synchronized (INSTANCE_LOCK) { 70 if (sInstance == null) { 71 sInstance = new A2dpNativeInterface(); 72 } 73 return sInstance; 74 } 75 } 76 77 /** Set singleton instance. */ 78 @VisibleForTesting setInstance(A2dpNativeInterface instance)79 public static void setInstance(A2dpNativeInterface instance) { 80 synchronized (INSTANCE_LOCK) { 81 sInstance = instance; 82 } 83 } 84 85 /** 86 * Initializes the native interface. 87 * 88 * @param maxConnectedAudioDevices maximum number of A2DP Sink devices that can be connected 89 * simultaneously 90 * @param codecConfigPriorities an array with the codec configuration priorities to configure. 91 */ init( int maxConnectedAudioDevices, BluetoothCodecConfig[] codecConfigPriorities, BluetoothCodecConfig[] codecConfigOffloading)92 public void init( 93 int maxConnectedAudioDevices, 94 BluetoothCodecConfig[] codecConfigPriorities, 95 BluetoothCodecConfig[] codecConfigOffloading) { 96 initNative(maxConnectedAudioDevices, codecConfigPriorities, codecConfigOffloading); 97 } 98 99 /** Cleanup the native interface. */ cleanup()100 public void cleanup() { 101 cleanupNative(); 102 } 103 104 /** Returns the list of locally supported codec types. */ getSupportedCodecTypes()105 public List<BluetoothCodecType> getSupportedCodecTypes() { 106 if (sSupportedCodecTypes == null) { 107 sSupportedCodecTypes = getSupportedCodecTypesNative(); 108 } 109 return Arrays.asList(sSupportedCodecTypes); 110 } 111 112 /** 113 * Initiates A2DP connection to a remote device. 114 * 115 * @param device the remote device 116 * @return true on success, otherwise false. 117 */ connectA2dp(BluetoothDevice device)118 public boolean connectA2dp(BluetoothDevice device) { 119 return connectA2dpNative(getByteAddress(device)); 120 } 121 122 /** 123 * Disconnects A2DP from a remote device. 124 * 125 * @param device the remote device 126 * @return true on success, otherwise false. 127 */ disconnectA2dp(BluetoothDevice device)128 public boolean disconnectA2dp(BluetoothDevice device) { 129 return disconnectA2dpNative(getByteAddress(device)); 130 } 131 132 /** 133 * Sets a connected A2DP remote device to silence mode. 134 * 135 * @param device the remote device 136 * @return true on success, otherwise false. 137 */ setSilenceDevice(BluetoothDevice device, boolean silence)138 public boolean setSilenceDevice(BluetoothDevice device, boolean silence) { 139 return setSilenceDeviceNative(getByteAddress(device), silence); 140 } 141 142 /** 143 * Sets a connected A2DP remote device as active. 144 * 145 * @param device the remote device 146 * @return true on success, otherwise false. 147 */ setActiveDevice(BluetoothDevice device)148 public boolean setActiveDevice(BluetoothDevice device) { 149 return setActiveDeviceNative(getByteAddress(device)); 150 } 151 152 /** 153 * Sets the codec configuration preferences. 154 * 155 * @param device the remote Bluetooth device 156 * @param codecConfigArray an array with the codec configurations to configure. 157 * @return true on success, otherwise false. 158 */ setCodecConfigPreference( BluetoothDevice device, BluetoothCodecConfig[] codecConfigArray)159 public boolean setCodecConfigPreference( 160 BluetoothDevice device, BluetoothCodecConfig[] codecConfigArray) { 161 return setCodecConfigPreferenceNative(getByteAddress(device), codecConfigArray); 162 } 163 getDevice(byte[] address)164 private BluetoothDevice getDevice(byte[] address) { 165 return mAdapterService.getDeviceFromByte(address); 166 } 167 getByteAddress(BluetoothDevice device)168 private byte[] getByteAddress(BluetoothDevice device) { 169 if (device == null) { 170 return Utils.getBytesFromAddress("00:00:00:00:00:00"); 171 } 172 if (Flags.identityAddressNullIfUnknown()) { 173 return Utils.getByteBrEdrAddress(device); 174 } else { 175 return mAdapterService.getByteIdentityAddress(device); 176 } 177 } 178 sendMessageToService(A2dpStackEvent event)179 private void sendMessageToService(A2dpStackEvent event) { 180 A2dpService service = A2dpService.getA2dpService(); 181 if (service != null) { 182 service.messageFromNative(event); 183 } else { 184 Log.w(TAG, "Event ignored, service not available: " + event); 185 } 186 } 187 188 // Callbacks from the native stack back into the Java framework. 189 // All callbacks are routed via the Service which will disambiguate which 190 // state machine the message should be routed to. 191 onConnectionStateChanged(byte[] address, int state)192 private void onConnectionStateChanged(byte[] address, int state) { 193 A2dpStackEvent event = 194 new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); 195 event.device = getDevice(address); 196 event.valueInt = state; 197 198 Log.d(TAG, "onConnectionStateChanged: " + event); 199 sendMessageToService(event); 200 } 201 onAudioStateChanged(byte[] address, int state)202 private void onAudioStateChanged(byte[] address, int state) { 203 A2dpStackEvent event = new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED); 204 event.device = getDevice(address); 205 event.valueInt = state; 206 207 Log.d(TAG, "onAudioStateChanged: " + event); 208 sendMessageToService(event); 209 } 210 onCodecConfigChanged( byte[] address, BluetoothCodecConfig newCodecConfig, BluetoothCodecConfig[] codecsLocalCapabilities, BluetoothCodecConfig[] codecsSelectableCapabilities)211 private void onCodecConfigChanged( 212 byte[] address, 213 BluetoothCodecConfig newCodecConfig, 214 BluetoothCodecConfig[] codecsLocalCapabilities, 215 BluetoothCodecConfig[] codecsSelectableCapabilities) { 216 A2dpStackEvent event = new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CODEC_CONFIG_CHANGED); 217 event.device = getDevice(address); 218 event.codecStatus = 219 new BluetoothCodecStatus( 220 newCodecConfig, 221 Arrays.asList(codecsLocalCapabilities), 222 Arrays.asList(codecsSelectableCapabilities)); 223 Log.d(TAG, "onCodecConfigChanged: " + event); 224 sendMessageToService(event); 225 } 226 isMandatoryCodecPreferred(byte[] address)227 private boolean isMandatoryCodecPreferred(byte[] address) { 228 A2dpService service = A2dpService.getA2dpService(); 229 if (service != null) { 230 int enabled = service.getOptionalCodecsEnabled(getDevice(address)); 231 Log.d(TAG, "isMandatoryCodecPreferred: optional preference " + enabled); 232 // Optional codecs are more preferred if possible 233 return enabled == BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED; 234 } else { 235 Log.w(TAG, "isMandatoryCodecPreferred: service not available"); 236 return false; 237 } 238 } 239 240 // Native methods that call into the JNI interface initNative( int maxConnectedAudioDevices, BluetoothCodecConfig[] codecConfigPriorities, BluetoothCodecConfig[] codecConfigOffloading)241 private native void initNative( 242 int maxConnectedAudioDevices, 243 BluetoothCodecConfig[] codecConfigPriorities, 244 BluetoothCodecConfig[] codecConfigOffloading); 245 cleanupNative()246 private native void cleanupNative(); 247 getSupportedCodecTypesNative()248 private native BluetoothCodecType[] getSupportedCodecTypesNative(); 249 connectA2dpNative(byte[] address)250 private native boolean connectA2dpNative(byte[] address); 251 disconnectA2dpNative(byte[] address)252 private native boolean disconnectA2dpNative(byte[] address); 253 setSilenceDeviceNative(byte[] address, boolean silence)254 private native boolean setSilenceDeviceNative(byte[] address, boolean silence); 255 setActiveDeviceNative(byte[] address)256 private native boolean setActiveDeviceNative(byte[] address); 257 setCodecConfigPreferenceNative( byte[] address, BluetoothCodecConfig[] codecConfigArray)258 private native boolean setCodecConfigPreferenceNative( 259 byte[] address, BluetoothCodecConfig[] codecConfigArray); 260 } 261