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.hap; 19 20 import android.bluetooth.BluetoothAdapter; 21 import android.bluetooth.BluetoothDevice; 22 import android.bluetooth.BluetoothHapPresetInfo; 23 import android.util.Log; 24 25 import com.android.bluetooth.Utils; 26 import com.android.internal.annotations.GuardedBy; 27 import com.android.internal.annotations.VisibleForTesting; 28 29 import java.util.ArrayList; 30 import java.util.Arrays; 31 32 /** 33 * Hearing Access Profile Client Native Interface to/from JNI. 34 */ 35 public class HapClientNativeInterface { 36 private static final String TAG = "HapClientNativeInterface"; 37 private static final boolean DBG = true; 38 private final BluetoothAdapter mAdapter; 39 40 @GuardedBy("INSTANCE_LOCK") 41 private static HapClientNativeInterface sInstance; 42 private static final Object INSTANCE_LOCK = new Object(); 43 44 static { classInitNative()45 classInitNative(); 46 } 47 HapClientNativeInterface()48 private HapClientNativeInterface() { 49 mAdapter = BluetoothAdapter.getDefaultAdapter(); 50 if (mAdapter == null) { 51 Log.wtf(TAG, "No Bluetooth Adapter Available"); 52 } 53 } 54 55 /** 56 * Get singleton instance. 57 */ getInstance()58 public static HapClientNativeInterface getInstance() { 59 synchronized (INSTANCE_LOCK) { 60 if (sInstance == null) { 61 sInstance = new HapClientNativeInterface(); 62 } 63 return sInstance; 64 } 65 } 66 67 /** 68 * Initiates HapClientService connection to a remote device. 69 * 70 * @param device the remote device 71 * @return true on success, otherwise false. 72 */ 73 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) connectHapClient(BluetoothDevice device)74 public boolean connectHapClient(BluetoothDevice device) { 75 return connectHapClientNative(getByteAddress(device)); 76 } 77 78 /** 79 * Disconnects HapClientService from a remote device. 80 * 81 * @param device the remote device 82 * @return true on success, otherwise false. 83 */ 84 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) disconnectHapClient(BluetoothDevice device)85 public boolean disconnectHapClient(BluetoothDevice device) { 86 return disconnectHapClientNative(getByteAddress(device)); 87 } 88 89 /** 90 * Gets a HapClientService device 91 * 92 * @param address the remote device address 93 * @return Bluetooth Device. 94 */ 95 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) getDevice(byte[] address)96 public BluetoothDevice getDevice(byte[] address) { 97 return mAdapter.getRemoteDevice(address); 98 } 99 getByteAddress(BluetoothDevice device)100 private byte[] getByteAddress(BluetoothDevice device) { 101 if (device == null) { 102 return Utils.getBytesFromAddress("00:00:00:00:00:00"); 103 } 104 return Utils.getBytesFromAddress(device.getAddress()); 105 } 106 107 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) sendMessageToService(HapClientStackEvent event)108 void sendMessageToService(HapClientStackEvent event) { 109 HapClientService service = HapClientService.getHapClientService(); 110 if (service != null) { 111 service.messageFromNative(event); 112 } else { 113 Log.e(TAG, "Event ignored, service not available: " + event); 114 } 115 } 116 117 /** 118 * Initializes the native interface. 119 */ 120 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) init()121 public void init() { 122 initNative(); 123 } 124 125 /** 126 * Cleanup the native interface. 127 */ 128 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) cleanup()129 public void cleanup() { 130 cleanupNative(); 131 } 132 133 /** 134 * Selects the currently active preset for a HA device 135 * 136 * @param device is the device for which we want to set the active preset 137 * @param presetIndex is an index of one of the available presets 138 */ 139 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) selectActivePreset(BluetoothDevice device, int presetIndex)140 public void selectActivePreset(BluetoothDevice device, int presetIndex) { 141 selectActivePresetNative(getByteAddress(device), presetIndex); 142 } 143 144 /** 145 * Selects the currently active preset for a HA device group. 146 * 147 * @param groupId is the device group identifier for which want to set the active preset 148 * @param presetIndex is an index of one of the available presets 149 */ 150 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) groupSelectActivePreset(int groupId, int presetIndex)151 public void groupSelectActivePreset(int groupId, int presetIndex) { 152 groupSelectActivePresetNative(groupId, presetIndex); 153 } 154 155 /** 156 * Sets the next preset as a currently active preset for a HA device 157 * 158 * @param device is the device for which we want to set the active preset 159 */ 160 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) nextActivePreset(BluetoothDevice device)161 public void nextActivePreset(BluetoothDevice device) { 162 nextActivePresetNative(getByteAddress(device)); 163 } 164 165 /** 166 * Sets the next preset as a currently active preset for a HA device group 167 * 168 * @param groupId is the device group identifier for which want to set the active preset 169 */ 170 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) groupNextActivePreset(int groupId)171 public void groupNextActivePreset(int groupId) { 172 groupNextActivePresetNative(groupId); 173 } 174 175 /** 176 * Sets the previous preset as a currently active preset for a HA device 177 * 178 * @param device is the device for which we want to set the active preset 179 */ 180 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) previousActivePreset(BluetoothDevice device)181 public void previousActivePreset(BluetoothDevice device) { 182 previousActivePresetNative(getByteAddress(device)); 183 } 184 185 /** 186 * Sets the previous preset as a currently active preset for a HA device group 187 * 188 * @param groupId is the device group identifier for which want to set the active preset 189 */ 190 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) groupPreviousActivePreset(int groupId)191 public void groupPreviousActivePreset(int groupId) { 192 groupPreviousActivePresetNative(groupId); 193 } 194 195 /** 196 * Requests the preset name 197 * 198 * @param device is the device for which we want to get the preset name 199 * @param presetIndex is an index of one of the available presets 200 */ 201 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) getPresetInfo(BluetoothDevice device, int presetIndex)202 public void getPresetInfo(BluetoothDevice device, int presetIndex) { 203 getPresetInfoNative(getByteAddress(device), presetIndex); 204 } 205 206 /** 207 * Sets the preset name 208 * 209 * @param device is the device for which we want to get the preset name 210 * @param presetIndex is an index of one of the available presets 211 * @param name is a new name for a preset 212 */ 213 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) setPresetName(BluetoothDevice device, int presetIndex, String name)214 public void setPresetName(BluetoothDevice device, int presetIndex, String name) { 215 setPresetNameNative(getByteAddress(device), presetIndex, name); 216 } 217 218 /** 219 * Sets the preset name 220 * 221 * @param groupId is the device group 222 * @param presetIndex is an index of one of the available presets 223 * @param name is a new name for a preset 224 */ 225 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) groupSetPresetName(int groupId, int presetIndex, String name)226 public void groupSetPresetName(int groupId, int presetIndex, String name) { 227 groupSetPresetNameNative(groupId, presetIndex, name); 228 } 229 230 // Callbacks from the native stack back into the Java framework. 231 // All callbacks are routed via the Service which will disambiguate which 232 // state machine the message should be routed to. 233 234 @VisibleForTesting onConnectionStateChanged(int state, byte[] address)235 void onConnectionStateChanged(int state, byte[] address) { 236 HapClientStackEvent event = 237 new HapClientStackEvent( 238 HapClientStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); 239 event.device = getDevice(address); 240 event.valueInt1 = state; 241 242 if (DBG) { 243 Log.d(TAG, "onConnectionStateChanged: " + event); 244 } 245 sendMessageToService(event); 246 } 247 248 @VisibleForTesting onDeviceAvailable(byte[] address, int features)249 void onDeviceAvailable(byte[] address, int features) { 250 HapClientStackEvent event = new HapClientStackEvent( 251 HapClientStackEvent.EVENT_TYPE_DEVICE_AVAILABLE); 252 event.device = getDevice(address); 253 event.valueInt1 = features; 254 255 if (DBG) { 256 Log.d(TAG, "onDeviceAvailable: " + event); 257 } 258 sendMessageToService(event); 259 } 260 261 @VisibleForTesting onFeaturesUpdate(byte[] address, int features)262 void onFeaturesUpdate(byte[] address, int features) { 263 HapClientStackEvent event = new HapClientStackEvent( 264 HapClientStackEvent.EVENT_TYPE_DEVICE_FEATURES); 265 event.device = getDevice(address); 266 event.valueInt1 = features; 267 268 if (DBG) { 269 Log.d(TAG, "onFeaturesUpdate: " + event); 270 } 271 sendMessageToService(event); 272 } 273 274 @VisibleForTesting onActivePresetSelected(byte[] address, int presetIndex)275 void onActivePresetSelected(byte[] address, int presetIndex) { 276 HapClientStackEvent event = new HapClientStackEvent( 277 HapClientStackEvent.EVENT_TYPE_ON_ACTIVE_PRESET_SELECTED); 278 event.device = getDevice(address); 279 event.valueInt1 = presetIndex; 280 281 if (DBG) { 282 Log.d(TAG, "onActivePresetSelected: " + event); 283 } 284 sendMessageToService(event); 285 } 286 287 @VisibleForTesting onActivePresetGroupSelected(int groupId, int presetIndex)288 void onActivePresetGroupSelected(int groupId, int presetIndex) { 289 HapClientStackEvent event = new HapClientStackEvent( 290 HapClientStackEvent.EVENT_TYPE_ON_ACTIVE_PRESET_SELECTED); 291 event.valueInt1 = presetIndex; 292 event.valueInt2 = groupId; 293 294 if (DBG) { 295 Log.d(TAG, "onActivePresetGroupSelected: " + event); 296 } 297 sendMessageToService(event); 298 } 299 300 @VisibleForTesting onActivePresetSelectError(byte[] address, int resultCode)301 void onActivePresetSelectError(byte[] address, int resultCode) { 302 HapClientStackEvent event = new HapClientStackEvent( 303 HapClientStackEvent.EVENT_TYPE_ON_ACTIVE_PRESET_SELECT_ERROR); 304 event.device = getDevice(address); 305 event.valueInt1 = resultCode; 306 307 if (DBG) { 308 Log.d(TAG, "onActivePresetSelectError: " + event); 309 } 310 sendMessageToService(event); 311 } 312 313 @VisibleForTesting onActivePresetGroupSelectError(int groupId, int resultCode)314 void onActivePresetGroupSelectError(int groupId, int resultCode) { 315 HapClientStackEvent event = new HapClientStackEvent( 316 HapClientStackEvent.EVENT_TYPE_ON_ACTIVE_PRESET_SELECT_ERROR); 317 event.valueInt1 = resultCode; 318 event.valueInt2 = groupId; 319 320 if (DBG) { 321 Log.d(TAG, "onActivePresetGroupSelectError: " + event); 322 } 323 sendMessageToService(event); 324 } 325 326 @VisibleForTesting onPresetInfo(byte[] address, int infoReason, BluetoothHapPresetInfo[] presets)327 void onPresetInfo(byte[] address, int infoReason, BluetoothHapPresetInfo[] presets) { 328 HapClientStackEvent event = new HapClientStackEvent( 329 HapClientStackEvent.EVENT_TYPE_ON_PRESET_INFO); 330 event.device = getDevice(address); 331 event.valueInt2 = infoReason; 332 event.valueList = new ArrayList<>(Arrays.asList(presets)); 333 334 if (DBG) { 335 Log.d(TAG, "onPresetInfo: " + event); 336 } 337 sendMessageToService(event); 338 } 339 340 @VisibleForTesting onGroupPresetInfo(int groupId, int infoReason, BluetoothHapPresetInfo[] presets)341 void onGroupPresetInfo(int groupId, int infoReason, BluetoothHapPresetInfo[] presets) { 342 HapClientStackEvent event = new HapClientStackEvent( 343 HapClientStackEvent.EVENT_TYPE_ON_PRESET_INFO); 344 event.valueInt2 = infoReason; 345 event.valueInt3 = groupId; 346 event.valueList = new ArrayList<>(Arrays.asList(presets)); 347 348 if (DBG) { 349 Log.d(TAG, "onPresetInfo: " + event); 350 } 351 sendMessageToService(event); 352 } 353 354 @VisibleForTesting onPresetNameSetError(byte[] address, int presetIndex, int resultCode)355 void onPresetNameSetError(byte[] address, int presetIndex, int resultCode) { 356 HapClientStackEvent event = new HapClientStackEvent( 357 HapClientStackEvent.EVENT_TYPE_ON_PRESET_NAME_SET_ERROR); 358 event.device = getDevice(address); 359 event.valueInt1 = resultCode; 360 event.valueInt2 = presetIndex; 361 362 if (DBG) { 363 Log.d(TAG, "OnPresetNameSetError: " + event); 364 } 365 sendMessageToService(event); 366 } 367 368 @VisibleForTesting onGroupPresetNameSetError(int groupId, int presetIndex, int resultCode)369 void onGroupPresetNameSetError(int groupId, int presetIndex, int resultCode) { 370 HapClientStackEvent event = new HapClientStackEvent( 371 HapClientStackEvent.EVENT_TYPE_ON_PRESET_NAME_SET_ERROR); 372 event.valueInt1 = resultCode; 373 event.valueInt2 = presetIndex; 374 event.valueInt3 = groupId; 375 376 if (DBG) { 377 Log.d(TAG, "OnPresetNameSetError: " + event); 378 } 379 sendMessageToService(event); 380 } 381 382 @VisibleForTesting onPresetInfoError(byte[] address, int presetIndex, int resultCode)383 void onPresetInfoError(byte[] address, int presetIndex, int resultCode) { 384 HapClientStackEvent event = new HapClientStackEvent( 385 HapClientStackEvent.EVENT_TYPE_ON_PRESET_INFO_ERROR); 386 event.device = getDevice(address); 387 event.valueInt1 = resultCode; 388 event.valueInt2 = presetIndex; 389 390 if (DBG) { 391 Log.d(TAG, "onPresetInfoError: " + event); 392 } 393 sendMessageToService(event); 394 } 395 396 @VisibleForTesting onGroupPresetInfoError(int groupId, int presetIndex, int resultCode)397 void onGroupPresetInfoError(int groupId, int presetIndex, int resultCode) { 398 HapClientStackEvent event = new HapClientStackEvent( 399 HapClientStackEvent.EVENT_TYPE_ON_PRESET_INFO_ERROR); 400 event.valueInt1 = resultCode; 401 event.valueInt2 = presetIndex; 402 event.valueInt3 = groupId; 403 404 if (DBG) { 405 Log.d(TAG, "onPresetInfoError: " + event); 406 } 407 sendMessageToService(event); 408 } 409 410 // Native methods that call into the JNI interface classInitNative()411 private static native void classInitNative(); initNative()412 private native void initNative(); cleanupNative()413 private native void cleanupNative(); connectHapClientNative(byte[] address)414 private native boolean connectHapClientNative(byte[] address); disconnectHapClientNative(byte[] address)415 private native boolean disconnectHapClientNative(byte[] address); selectActivePresetNative(byte[] byteAddress, int presetIndex)416 private native void selectActivePresetNative(byte[] byteAddress, int presetIndex); groupSelectActivePresetNative(int groupId, int presetIndex)417 private native void groupSelectActivePresetNative(int groupId, int presetIndex); nextActivePresetNative(byte[] byteAddress)418 private native void nextActivePresetNative(byte[] byteAddress); groupNextActivePresetNative(int groupId)419 private native void groupNextActivePresetNative(int groupId); previousActivePresetNative(byte[] byteAddress)420 private native void previousActivePresetNative(byte[] byteAddress); groupPreviousActivePresetNative(int groupId)421 private native void groupPreviousActivePresetNative(int groupId); getPresetInfoNative(byte[] byteAddress, int presetIndex)422 private native void getPresetInfoNative(byte[] byteAddress, int presetIndex); setPresetNameNative(byte[] byteAddress, int presetIndex, String name)423 private native void setPresetNameNative(byte[] byteAddress, int presetIndex, String name); groupSetPresetNameNative(int groupId, int presetIndex, String name)424 private native void groupSetPresetNameNative(int groupId, int presetIndex, String name); 425 } 426