1 /* 2 * Copyright 2021 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 package com.android.bluetooth.vc; 19 20 import android.bluetooth.BluetoothAdapter; 21 import android.bluetooth.BluetoothDevice; 22 import android.util.Log; 23 24 import com.android.bluetooth.Utils; 25 import com.android.internal.annotations.GuardedBy; 26 import com.android.internal.annotations.VisibleForTesting; 27 28 public class VolumeControlNativeInterface { 29 private static final String TAG = "VolumeControlNativeInterface"; 30 private static final boolean DBG = true; 31 private BluetoothAdapter mAdapter; 32 33 @GuardedBy("INSTANCE_LOCK") 34 private static VolumeControlNativeInterface sInstance; 35 private static final Object INSTANCE_LOCK = new Object(); 36 37 static { classInitNative()38 classInitNative(); 39 } 40 VolumeControlNativeInterface()41 private VolumeControlNativeInterface() { 42 mAdapter = BluetoothAdapter.getDefaultAdapter(); 43 if (mAdapter == null) { 44 Log.wtf(TAG, "No Bluetooth Adapter Available"); 45 } 46 } 47 48 /** 49 * Get singleton instance. 50 */ getInstance()51 public static VolumeControlNativeInterface getInstance() { 52 synchronized (INSTANCE_LOCK) { 53 if (sInstance == null) { 54 sInstance = new VolumeControlNativeInterface(); 55 } 56 return sInstance; 57 } 58 } 59 60 /** 61 * Initializes the native interface. 62 * 63 * priorities to configure. 64 */ 65 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) init()66 public void init() { 67 initNative(); 68 } 69 70 /** 71 * Cleanup the native interface. 72 */ 73 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) cleanup()74 public void cleanup() { 75 cleanupNative(); 76 } 77 78 /** 79 * Initiates VolumeControl connection to a remote device. 80 * 81 * @param device the remote device 82 * @return true on success, otherwise false. 83 */ 84 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) connectVolumeControl(BluetoothDevice device)85 public boolean connectVolumeControl(BluetoothDevice device) { 86 return connectVolumeControlNative(getByteAddress(device)); 87 } 88 89 /** 90 * Disconnects VolumeControl from a remote device. 91 * 92 * @param device the remote device 93 * @return true on success, otherwise false. 94 */ 95 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) disconnectVolumeControl(BluetoothDevice device)96 public boolean disconnectVolumeControl(BluetoothDevice device) { 97 return disconnectVolumeControlNative(getByteAddress(device)); 98 } 99 100 /** 101 * Sets the VolumeControl volume 102 * @param device 103 * @param volume 104 */ 105 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) setVolume(BluetoothDevice device, int volume)106 public void setVolume(BluetoothDevice device, int volume) { 107 setVolumeNative(getByteAddress(device), volume); 108 } 109 110 /** 111 * Sets the VolumeControl volume for the group 112 * @param groupId 113 * @param volume 114 */ 115 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) setGroupVolume(int groupId, int volume)116 public void setGroupVolume(int groupId, int volume) { 117 setGroupVolumeNative(groupId, volume); 118 } 119 120 /** 121 * Mute the VolumeControl volume 122 * @param device 123 * @param unmute 124 */ 125 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) mute(BluetoothDevice device)126 public void mute(BluetoothDevice device) { 127 muteNative(getByteAddress(device)); 128 } 129 130 /** 131 * Mute the VolumeControl volume in the group 132 * @param groupId 133 * @param unmute 134 */ 135 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) muteGroup(int groupId)136 public void muteGroup(int groupId) { 137 muteGroupNative(groupId); 138 } 139 140 /** 141 * Unmute the VolumeControl volume 142 */ 143 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) unmute(BluetoothDevice device)144 public void unmute(BluetoothDevice device) { 145 unmuteNative(getByteAddress(device)); 146 } 147 148 /** 149 * Unmute the VolumeControl volume group 150 * @param groupId 151 * @param unmute 152 */ 153 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) unmuteGroup(int groupId)154 public void unmuteGroup(int groupId) { 155 unmuteGroupNative(groupId); 156 } 157 158 /** 159 * Gets external audio output volume offset from a remote device. 160 * 161 * @param device the remote device 162 * @param externalOutputId external audio output id 163 * @return true on success, otherwise false. 164 */ 165 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) getExtAudioOutVolumeOffset(BluetoothDevice device, int externalOutputId)166 public boolean getExtAudioOutVolumeOffset(BluetoothDevice device, int externalOutputId) { 167 return getExtAudioOutVolumeOffsetNative(getByteAddress(device), externalOutputId); 168 } 169 170 /** 171 * Sets external audio output volume offset to a remote device. 172 * 173 * @param device the remote device 174 * @param externalOutputId external audio output id 175 * @param offset requested offset 176 * @return true on success, otherwise false. 177 */ 178 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) setExtAudioOutVolumeOffset(BluetoothDevice device, int externalOutputId, int offset)179 public boolean setExtAudioOutVolumeOffset(BluetoothDevice device, int externalOutputId, 180 int offset) { 181 if (Utils.isPtsTestMode()) { 182 setVolumeNative(getByteAddress(device), offset); 183 return true; 184 } 185 return setExtAudioOutVolumeOffsetNative(getByteAddress(device), externalOutputId, offset); 186 } 187 188 /** 189 * Gets external audio output location from a remote device. 190 * 191 * @param device the remote device 192 * @param externalOutputId external audio output id 193 * @return true on success, otherwise false. 194 */ 195 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) getExtAudioOutLocation(BluetoothDevice device, int externalOutputId)196 public boolean getExtAudioOutLocation(BluetoothDevice device, int externalOutputId) { 197 return getExtAudioOutLocationNative(getByteAddress(device), externalOutputId); 198 } 199 200 /** 201 * Sets external audio volume offset to a remote device. 202 * 203 * @param device the remote device 204 * @param externalOutputId external audio output id 205 * @param location requested location 206 * @return true on success, otherwise false. 207 */ 208 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) setExtAudioOutLocation(BluetoothDevice device, int externalOutputId, int location)209 public boolean setExtAudioOutLocation(BluetoothDevice device, int externalOutputId, 210 int location) { 211 return setExtAudioOutLocationNative(getByteAddress(device), externalOutputId, location); 212 } 213 214 /** 215 * Gets external audio output description from a remote device. 216 * 217 * @param device the remote device 218 * @param externalOutputId external audio output id 219 * @return true on success, otherwise false. 220 */ 221 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) getExtAudioOutDescription(BluetoothDevice device, int externalOutputId)222 public boolean getExtAudioOutDescription(BluetoothDevice device, int externalOutputId) { 223 return getExtAudioOutDescriptionNative(getByteAddress(device), externalOutputId); 224 } 225 226 /** 227 * Sets external audio volume description to a remote device. 228 * 229 * @param device the remote device 230 * @param externalOutputId external audio output id 231 * @param descr requested description 232 * @return true on success, otherwise false. 233 */ 234 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) setExtAudioOutDescription(BluetoothDevice device, int externalOutputId, String descr)235 public boolean setExtAudioOutDescription(BluetoothDevice device, int externalOutputId, 236 String descr) { 237 return setExtAudioOutDescriptionNative(getByteAddress(device), externalOutputId, descr); 238 } 239 getDevice(byte[] address)240 private BluetoothDevice getDevice(byte[] address) { 241 return mAdapter.getRemoteDevice(address); 242 } 243 getByteAddress(BluetoothDevice device)244 private byte[] getByteAddress(BluetoothDevice device) { 245 if (device == null) { 246 return Utils.getBytesFromAddress("00:00:00:00:00:00"); 247 } 248 return Utils.getBytesFromAddress(device.getAddress()); 249 } 250 sendMessageToService(VolumeControlStackEvent event)251 private void sendMessageToService(VolumeControlStackEvent event) { 252 VolumeControlService service = VolumeControlService.getVolumeControlService(); 253 if (service != null) { 254 service.messageFromNative(event); 255 } else { 256 Log.e(TAG, "Event ignored, service not available: " + event); 257 } 258 } 259 260 // Callbacks from the native stack back into the Java framework. 261 // All callbacks are routed via the Service which will disambiguate which 262 // state machine the message should be routed to. 263 @VisibleForTesting onConnectionStateChanged(int state, byte[] address)264 void onConnectionStateChanged(int state, byte[] address) { 265 VolumeControlStackEvent event = 266 new VolumeControlStackEvent( 267 VolumeControlStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); 268 event.device = getDevice(address); 269 event.valueInt1 = state; 270 271 if (DBG) { 272 Log.d(TAG, "onConnectionStateChanged: " + event); 273 } 274 sendMessageToService(event); 275 } 276 277 @VisibleForTesting onVolumeStateChanged(int volume, boolean mute, byte[] address, boolean isAutonomous)278 void onVolumeStateChanged(int volume, boolean mute, byte[] address, 279 boolean isAutonomous) { 280 VolumeControlStackEvent event = 281 new VolumeControlStackEvent( 282 VolumeControlStackEvent.EVENT_TYPE_VOLUME_STATE_CHANGED); 283 event.device = getDevice(address); 284 event.valueInt1 = -1; 285 event.valueInt2 = volume; 286 event.valueBool1 = mute; 287 event.valueBool2 = isAutonomous; 288 289 if (DBG) { 290 Log.d(TAG, "onVolumeStateChanged: " + event); 291 } 292 sendMessageToService(event); 293 } 294 295 @VisibleForTesting onGroupVolumeStateChanged(int volume, boolean mute, int groupId, boolean isAutonomous)296 void onGroupVolumeStateChanged(int volume, boolean mute, int groupId, 297 boolean isAutonomous) { 298 VolumeControlStackEvent event = 299 new VolumeControlStackEvent( 300 VolumeControlStackEvent.EVENT_TYPE_VOLUME_STATE_CHANGED); 301 event.device = null; 302 event.valueInt1 = groupId; 303 event.valueInt2 = volume; 304 event.valueBool1 = mute; 305 event.valueBool2 = isAutonomous; 306 307 if (DBG) { 308 Log.d(TAG, "onGroupVolumeStateChanged: " + event); 309 } 310 sendMessageToService(event); 311 } 312 313 @VisibleForTesting onDeviceAvailable(int numOfExternalOutputs, byte[] address)314 void onDeviceAvailable(int numOfExternalOutputs, 315 byte[] address) { 316 VolumeControlStackEvent event = 317 new VolumeControlStackEvent( 318 VolumeControlStackEvent.EVENT_TYPE_DEVICE_AVAILABLE); 319 event.device = getDevice(address); 320 event.valueInt1 = numOfExternalOutputs; 321 322 if (DBG) { 323 Log.d(TAG, "onDeviceAvailable: " + event); 324 } 325 sendMessageToService(event); 326 } 327 328 @VisibleForTesting onExtAudioOutVolumeOffsetChanged(int externalOutputId, int offset, byte[] address)329 void onExtAudioOutVolumeOffsetChanged(int externalOutputId, int offset, 330 byte[] address) { 331 VolumeControlStackEvent event = 332 new VolumeControlStackEvent( 333 VolumeControlStackEvent.EVENT_TYPE_EXT_AUDIO_OUT_VOL_OFFSET_CHANGED); 334 event.device = getDevice(address); 335 event.valueInt1 = externalOutputId; 336 event.valueInt2 = offset; 337 338 if (DBG) { 339 Log.d(TAG, "onExtAudioOutVolumeOffsetChanged: " + event); 340 } 341 sendMessageToService(event); 342 } 343 344 @VisibleForTesting onExtAudioOutLocationChanged(int externalOutputId, int location, byte[] address)345 void onExtAudioOutLocationChanged(int externalOutputId, int location, 346 byte[] address) { 347 VolumeControlStackEvent event = 348 new VolumeControlStackEvent( 349 VolumeControlStackEvent.EVENT_TYPE_EXT_AUDIO_OUT_LOCATION_CHANGED); 350 event.device = getDevice(address); 351 event.valueInt1 = externalOutputId; 352 event.valueInt2 = location; 353 354 if (DBG) { 355 Log.d(TAG, "onExtAudioOutLocationChanged: " + event); 356 } 357 sendMessageToService(event); 358 } 359 360 @VisibleForTesting onExtAudioOutDescriptionChanged(int externalOutputId, String descr, byte[] address)361 void onExtAudioOutDescriptionChanged(int externalOutputId, String descr, 362 byte[] address) { 363 VolumeControlStackEvent event = 364 new VolumeControlStackEvent( 365 VolumeControlStackEvent.EVENT_TYPE_EXT_AUDIO_OUT_DESCRIPTION_CHANGED); 366 event.device = getDevice(address); 367 event.valueInt1 = externalOutputId; 368 event.valueString1 = descr; 369 370 if (DBG) { 371 Log.d(TAG, "onExtAudioOutLocationChanged: " + event); 372 } 373 sendMessageToService(event); 374 } 375 376 // Native methods that call into the JNI interface classInitNative()377 private static native void classInitNative(); initNative()378 private native void initNative(); cleanupNative()379 private native void cleanupNative(); connectVolumeControlNative(byte[] address)380 private native boolean connectVolumeControlNative(byte[] address); disconnectVolumeControlNative(byte[] address)381 private native boolean disconnectVolumeControlNative(byte[] address); setVolumeNative(byte[] address, int volume)382 private native void setVolumeNative(byte[] address, int volume); setGroupVolumeNative(int groupId, int volume)383 private native void setGroupVolumeNative(int groupId, int volume); muteNative(byte[] address)384 private native void muteNative(byte[] address); muteGroupNative(int groupId)385 private native void muteGroupNative(int groupId); unmuteNative(byte[] address)386 private native void unmuteNative(byte[] address); unmuteGroupNative(int groupId)387 private native void unmuteGroupNative(int groupId); getExtAudioOutVolumeOffsetNative(byte[] address, int externalOutputId)388 private native boolean getExtAudioOutVolumeOffsetNative(byte[] address, int externalOutputId); setExtAudioOutVolumeOffsetNative(byte[] address, int externalOutputId, int offset)389 private native boolean setExtAudioOutVolumeOffsetNative(byte[] address, int externalOutputId, 390 int offset); getExtAudioOutLocationNative(byte[] address, int externalOutputId)391 private native boolean getExtAudioOutLocationNative(byte[] address, int externalOutputId); setExtAudioOutLocationNative(byte[] address, int externalOutputId, int location)392 private native boolean setExtAudioOutLocationNative(byte[] address, int externalOutputId, 393 int location); getExtAudioOutDescriptionNative(byte[] address, int externalOutputId)394 private native boolean getExtAudioOutDescriptionNative(byte[] address, int externalOutputId); setExtAudioOutDescriptionNative(byte[] address, int externalOutputId, String descr)395 private native boolean setExtAudioOutDescriptionNative(byte[] address, int externalOutputId, 396 String descr); 397 } 398