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