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.leaudio; 19 20 import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; 21 import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; 22 import static android.bluetooth.BluetoothProfile.STATE_CONNECTED; 23 import static android.bluetooth.BluetoothProfile.STATE_DISCONNECTED; 24 25 import android.app.Application; 26 import android.bluetooth.*; 27 import android.content.BroadcastReceiver; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.content.IntentFilter; 31 import android.os.ParcelUuid; 32 import android.util.Log; 33 34 import androidx.annotation.Nullable; 35 import androidx.core.util.Pair; 36 import androidx.lifecycle.LiveData; 37 import androidx.lifecycle.MutableLiveData; 38 39 import java.lang.reflect.InvocationTargetException; 40 import java.lang.reflect.Method; 41 import java.util.ArrayList; 42 import java.util.Arrays; 43 import java.util.Collections; 44 import java.util.HashMap; 45 import java.util.HashSet; 46 import java.util.List; 47 import java.util.ListIterator; 48 import java.util.Map; 49 import java.util.Objects; 50 import java.util.Optional; 51 import java.util.Set; 52 import java.util.UUID; 53 import java.util.concurrent.ExecutorService; 54 import java.util.concurrent.Executors; 55 import java.util.stream.Collectors; 56 57 public class BluetoothProxy { 58 private static BluetoothProxy INSTANCE; 59 private final Application application; 60 private final BluetoothAdapter bluetoothAdapter; 61 private BluetoothLeAudio bluetoothLeAudio = null; 62 private BluetoothLeBroadcast mBluetoothLeBroadcast = null; 63 private BluetoothLeBroadcastAssistant mBluetoothLeBroadcastAssistant = null; 64 private Set<BluetoothDevice> mBroadcastScanDelegatorDevices = new HashSet<>(); 65 private BluetoothCsipSetCoordinator bluetoothCsis = null; 66 private BluetoothVolumeControl bluetoothVolumeControl = null; 67 private BluetoothHapClient bluetoothHapClient = null; 68 private Map<LeAudioDeviceStateWrapper, BluetoothGatt> bluetoothGattMap = new HashMap<>(); 69 private BluetoothProfile.ServiceListener profileListener = null; 70 private BluetoothHapClient.Callback hapCallback = null; 71 private OnBassEventListener mBassEventListener; 72 private OnLocalBroadcastEventListener mLocalBroadcastEventListener; 73 private final IntentFilter adapterIntentFilter; 74 private final IntentFilter bassIntentFilter; 75 private IntentFilter intentFilter; 76 private final ExecutorService mExecutor; 77 78 private final Map<Integer, UUID> mGroupLocks = new HashMap<>(); 79 80 private int GROUP_NODE_ADDED = 1; 81 private int GROUP_NODE_REMOVED = 2; 82 83 private boolean mLeAudioCallbackRegistered = false; 84 private BluetoothLeAudio.Callback mLeAudioCallbacks = 85 new BluetoothLeAudio.Callback() { 86 @Override 87 public void onCodecConfigChanged(int groupId, BluetoothLeAudioCodecStatus status) {} 88 89 @Override 90 public void onGroupStatusChanged(int groupId, int groupStatus) { 91 List<LeAudioDeviceStateWrapper> valid_devices = null; 92 valid_devices = 93 allLeAudioDevicesMutable.getValue().stream() 94 .filter( 95 state -> 96 state.leAudioData != null 97 && state.leAudioData.nodeStatusMutable 98 .getValue() 99 != null 100 && state.leAudioData 101 .nodeStatusMutable 102 .getValue() 103 .first 104 .equals(groupId)) 105 .collect(Collectors.toList()); 106 for (LeAudioDeviceStateWrapper dev : valid_devices) { 107 dev.leAudioData.groupStatusMutable.postValue( 108 new Pair<>(groupId, new Pair<>(groupStatus, 0))); 109 } 110 } 111 112 @Override 113 public void onGroupNodeAdded(BluetoothDevice device, int groupId) { 114 Log.d("LeCB:", device + " group added " + groupId); 115 if (device == null || groupId == BluetoothLeAudio.GROUP_ID_INVALID) { 116 Log.d("LeCB:", "invalid parameter"); 117 return; 118 } 119 Optional<LeAudioDeviceStateWrapper> valid_device_opt = 120 allLeAudioDevicesMutable.getValue().stream() 121 .filter( 122 state -> 123 state.device 124 .getAddress() 125 .equals(device.getAddress())) 126 .findAny(); 127 128 if (!valid_device_opt.isPresent()) { 129 Log.d("LeCB:", "Device not present"); 130 return; 131 } 132 133 LeAudioDeviceStateWrapper valid_device = valid_device_opt.get(); 134 LeAudioDeviceStateWrapper.LeAudioData svc_data = valid_device.leAudioData; 135 136 svc_data.nodeStatusMutable.postValue(new Pair<>(groupId, GROUP_NODE_ADDED)); 137 svc_data.groupStatusMutable.postValue(new Pair<>(groupId, new Pair<>(-1, -1))); 138 } 139 140 @Override 141 public void onGroupNodeRemoved(BluetoothDevice device, int groupId) { 142 if (device == null || groupId == BluetoothLeAudio.GROUP_ID_INVALID) { 143 Log.d("LeCB:", "invalid parameter"); 144 return; 145 } 146 147 Log.d("LeCB:", device + " group added " + groupId); 148 if (device == null || groupId == BluetoothLeAudio.GROUP_ID_INVALID) { 149 Log.d("LeCB:", "invalid parameter"); 150 return; 151 } 152 153 Optional<LeAudioDeviceStateWrapper> valid_device_opt = 154 allLeAudioDevicesMutable.getValue().stream() 155 .filter( 156 state -> 157 state.device 158 .getAddress() 159 .equals(device.getAddress())) 160 .findAny(); 161 162 if (!valid_device_opt.isPresent()) { 163 Log.d("LeCB:", "Device not present"); 164 return; 165 } 166 167 LeAudioDeviceStateWrapper valid_device = valid_device_opt.get(); 168 LeAudioDeviceStateWrapper.LeAudioData svc_data = valid_device.leAudioData; 169 170 svc_data.nodeStatusMutable.postValue(new Pair<>(groupId, GROUP_NODE_REMOVED)); 171 svc_data.groupStatusMutable.postValue(new Pair<>(groupId, new Pair<>(-1, -1))); 172 } 173 }; 174 175 private final MutableLiveData<Boolean> enabledBluetoothMutable; 176 private final BroadcastReceiver adapterIntentReceiver = 177 new BroadcastReceiver() { 178 @Override 179 public void onReceive(Context context, Intent intent) { 180 String action = intent.getAction(); 181 if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) { 182 int toState = 183 intent.getIntExtra( 184 BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); 185 if (toState == BluetoothAdapter.STATE_ON) { 186 enabledBluetoothMutable.postValue(true); 187 } else if (toState == BluetoothAdapter.STATE_OFF) { 188 enabledBluetoothMutable.postValue(false); 189 } 190 } 191 } 192 }; 193 private final MutableLiveData<List<LeAudioDeviceStateWrapper>> allLeAudioDevicesMutable; 194 private final BroadcastReceiver leAudioIntentReceiver = 195 new BroadcastReceiver() { 196 @Override 197 public void onReceive(Context context, Intent intent) { 198 String action = intent.getAction(); 199 final BluetoothDevice device = 200 intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 201 202 if (allLeAudioDevicesMutable.getValue() != null) { 203 if (device != null) { 204 Optional<LeAudioDeviceStateWrapper> valid_device_opt = 205 allLeAudioDevicesMutable.getValue().stream() 206 .filter( 207 state -> 208 state.device 209 .getAddress() 210 .equals(device.getAddress())) 211 .findAny(); 212 213 if (valid_device_opt.isPresent()) { 214 LeAudioDeviceStateWrapper valid_device = valid_device_opt.get(); 215 LeAudioDeviceStateWrapper.LeAudioData svc_data = 216 valid_device.leAudioData; 217 int group_id; 218 219 // Handle Le Audio actions 220 switch (action) { 221 case BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED: 222 { 223 final int toState = 224 intent.getIntExtra( 225 BluetoothLeAudio.EXTRA_STATE, -1); 226 if (toState == BluetoothLeAudio.STATE_CONNECTED 227 || toState 228 == BluetoothLeAudio.STATE_DISCONNECTED) 229 svc_data.isConnectedMutable.postValue( 230 toState 231 == BluetoothLeAudio 232 .STATE_CONNECTED); 233 234 group_id = bluetoothLeAudio.getGroupId(device); 235 svc_data.nodeStatusMutable.postValue( 236 new Pair<>(group_id, GROUP_NODE_ADDED)); 237 svc_data.groupStatusMutable.postValue( 238 new Pair<>(group_id, new Pair<>(-1, -1))); 239 break; 240 } 241 } 242 } 243 } 244 } 245 } 246 }; 247 248 private final BroadcastReceiver hapClientIntentReceiver = 249 new BroadcastReceiver() { 250 @Override 251 public void onReceive(Context context, Intent intent) { 252 String action = intent.getAction(); 253 final BluetoothDevice device = 254 intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 255 256 if (allLeAudioDevicesMutable.getValue() != null) { 257 if (device != null) { 258 Optional<LeAudioDeviceStateWrapper> valid_device_opt = 259 allLeAudioDevicesMutable.getValue().stream() 260 .filter( 261 state -> 262 state.device 263 .getAddress() 264 .equals(device.getAddress())) 265 .findAny(); 266 267 if (valid_device_opt.isPresent()) { 268 LeAudioDeviceStateWrapper valid_device = valid_device_opt.get(); 269 LeAudioDeviceStateWrapper.HapData svc_data = valid_device.hapData; 270 271 switch (action) { 272 case BluetoothHapClient.ACTION_HAP_CONNECTION_STATE_CHANGED: 273 { 274 final int toState = 275 intent.getIntExtra( 276 BluetoothHapClient.EXTRA_STATE, -1); 277 svc_data.hapStateMutable.postValue(toState); 278 break; 279 } 280 // Hidden API 281 case "android.bluetooth.action.HAP_DEVICE_AVAILABLE": 282 { 283 final int features = 284 intent.getIntExtra( 285 "android.bluetooth.extra.HAP_FEATURES", 286 -1); 287 svc_data.hapFeaturesMutable.postValue(features); 288 break; 289 } 290 default: 291 // Do nothing 292 break; 293 } 294 } 295 } 296 } 297 } 298 }; 299 300 private final BroadcastReceiver volumeControlIntentReceiver = 301 new BroadcastReceiver() { 302 @Override 303 public void onReceive(Context context, Intent intent) { 304 String action = intent.getAction(); 305 final BluetoothDevice device = 306 intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 307 308 if (allLeAudioDevicesMutable.getValue() != null) { 309 if (device != null) { 310 Optional<LeAudioDeviceStateWrapper> valid_device_opt = 311 allLeAudioDevicesMutable.getValue().stream() 312 .filter( 313 state -> 314 state.device 315 .getAddress() 316 .equals(device.getAddress())) 317 .findAny(); 318 319 if (valid_device_opt.isPresent()) { 320 LeAudioDeviceStateWrapper valid_device = valid_device_opt.get(); 321 LeAudioDeviceStateWrapper.VolumeControlData svc_data = 322 valid_device.volumeControlData; 323 324 switch (action) { 325 case BluetoothVolumeControl.ACTION_CONNECTION_STATE_CHANGED: 326 final int toState = 327 intent.getIntExtra( 328 BluetoothVolumeControl.EXTRA_STATE, -1); 329 if (toState == BluetoothVolumeControl.STATE_CONNECTED 330 || toState 331 == BluetoothVolumeControl 332 .STATE_DISCONNECTED) 333 svc_data.isConnectedMutable.postValue( 334 toState 335 == BluetoothVolumeControl 336 .STATE_CONNECTED); 337 break; 338 } 339 } 340 } 341 } 342 } 343 }; 344 private final MutableLiveData<BluetoothLeBroadcastMetadata> mBroadcastUpdateMutableLive; 345 private final MutableLiveData<Pair<Integer /* reason */, Integer /* broadcastId */>> 346 mBroadcastPlaybackStartedMutableLive; 347 private final MutableLiveData<Pair<Integer /* reason */, Integer /* broadcastId */>> 348 mBroadcastPlaybackStoppedMutableLive; 349 private final MutableLiveData<Integer /* broadcastId */> mBroadcastAddedMutableLive; 350 private final MutableLiveData<Pair<Integer /* reason */, Integer /* broadcastId */>> 351 mBroadcastRemovedMutableLive; 352 private final MutableLiveData<String> mBroadcastStatusMutableLive; 353 private final BluetoothLeBroadcast.Callback mBroadcasterCallback = 354 new BluetoothLeBroadcast.Callback() { 355 @Override 356 public void onBroadcastStarted(int reason, int broadcastId) { 357 if ((reason != BluetoothStatusCodes.REASON_LOCAL_APP_REQUEST) 358 && (reason != BluetoothStatusCodes.REASON_LOCAL_STACK_REQUEST)) { 359 mBroadcastStatusMutableLive.postValue( 360 "Unable to create broadcast: " 361 + broadcastId 362 + ", reason: " 363 + reason); 364 } 365 366 mBroadcastAddedMutableLive.postValue(broadcastId); 367 if (mLocalBroadcastEventListener != null) { 368 mLocalBroadcastEventListener.onBroadcastStarted(broadcastId); 369 } 370 } 371 372 @Override 373 public void onBroadcastStartFailed(int reason) { 374 mBroadcastStatusMutableLive.postValue( 375 "Unable to START broadcast due to reason: " + reason); 376 } 377 378 @Override 379 public void onBroadcastStopped(int reason, int broadcastId) { 380 mBroadcastRemovedMutableLive.postValue(new Pair<>(reason, broadcastId)); 381 if (mLocalBroadcastEventListener != null) { 382 mLocalBroadcastEventListener.onBroadcastStopped(broadcastId); 383 } 384 } 385 386 @Override 387 public void onBroadcastStopFailed(int reason) { 388 mBroadcastStatusMutableLive.postValue( 389 "Unable to STOP broadcast due to reason: " + reason); 390 } 391 392 @Override 393 public void onPlaybackStarted(int reason, int broadcastId) { 394 mBroadcastPlaybackStartedMutableLive.postValue(new Pair<>(reason, broadcastId)); 395 } 396 397 @Override 398 public void onPlaybackStopped(int reason, int broadcastId) { 399 mBroadcastPlaybackStoppedMutableLive.postValue(new Pair<>(reason, broadcastId)); 400 } 401 402 @Override 403 public void onBroadcastUpdated(int reason, int broadcastId) { 404 mBroadcastStatusMutableLive.postValue( 405 "Broadcast " 406 + broadcastId 407 + "has been updated due to reason: " 408 + reason); 409 if (mLocalBroadcastEventListener != null) { 410 mLocalBroadcastEventListener.onBroadcastUpdated(broadcastId); 411 } 412 } 413 414 @Override 415 public void onBroadcastUpdateFailed(int reason, int broadcastId) { 416 mBroadcastStatusMutableLive.postValue( 417 "Unable to UPDATE broadcast " 418 + broadcastId 419 + " due to reason: " 420 + reason); 421 } 422 423 @Override 424 public void onBroadcastMetadataChanged( 425 int broadcastId, BluetoothLeBroadcastMetadata metadata) { 426 mBroadcastUpdateMutableLive.postValue(metadata); 427 if (mLocalBroadcastEventListener != null) { 428 mLocalBroadcastEventListener.onBroadcastMetadataChanged( 429 broadcastId, metadata); 430 } 431 } 432 }; 433 434 // TODO: Add behaviors in empty methods if necessary. 435 private final BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback = 436 new BluetoothLeBroadcastAssistant.Callback() { 437 @Override 438 public void onSearchStarted(int reason) {} 439 440 @Override 441 public void onSearchStartFailed(int reason) {} 442 443 @Override 444 public void onSearchStopped(int reason) {} 445 446 @Override 447 public void onSearchStopFailed(int reason) {} 448 449 @Override 450 public void onSourceFound(BluetoothLeBroadcastMetadata source) { 451 Log.d("BluetoothProxy", "onSourceFound"); 452 if (mBassEventListener != null) { 453 mBassEventListener.onSourceFound(source); 454 } 455 } 456 457 @Override 458 public void onSourceAdded(BluetoothDevice sink, int sourceId, int reason) {} 459 460 @Override 461 public void onSourceAddFailed( 462 BluetoothDevice sink, BluetoothLeBroadcastMetadata source, int reason) {} 463 464 @Override 465 public void onSourceModified(BluetoothDevice sink, int sourceId, int reason) {} 466 467 @Override 468 public void onSourceModifyFailed(BluetoothDevice sink, int sourceId, int reason) {} 469 470 @Override 471 public void onSourceRemoved(BluetoothDevice sink, int sourceId, int reason) {} 472 473 @Override 474 public void onSourceRemoveFailed(BluetoothDevice sink, int sourceId, int reason) {} 475 476 @Override 477 public void onReceiveStateChanged( 478 BluetoothDevice sink, 479 int sourceId, 480 BluetoothLeBroadcastReceiveState state) { 481 Log.d("BluetoothProxy", "onReceiveStateChanged"); 482 if (allLeAudioDevicesMutable.getValue() != null) { 483 Optional<LeAudioDeviceStateWrapper> valid_device_opt = 484 allLeAudioDevicesMutable.getValue().stream() 485 .filter( 486 stateWrapper -> 487 stateWrapper 488 .device 489 .getAddress() 490 .equals(sink.getAddress())) 491 .findAny(); 492 493 if (!valid_device_opt.isPresent()) return; 494 495 LeAudioDeviceStateWrapper valid_device = valid_device_opt.get(); 496 LeAudioDeviceStateWrapper.BassData svc_data = valid_device.bassData; 497 498 /** 499 * From "Introducing-Bluetooth-LE-Audio-book" 8.6.3.1: 500 * 501 * <p>The Source_ID is an Acceptor generated number which is used to 502 * identify a specific set of broadcast device and BIG information. It is 503 * local to an Acceptor and used as a reference for a Broadcast Assistant. 504 * In the case of a Coordinated Set of Acceptors, such as a left and right 505 * earbud, the Source_IDs are not related and may be different, even if both 506 * are receiving the same BIS, as each Acceptor independently creates their 507 * own Source ID values 508 */ 509 510 /** Broadcast receiver's endpoint identifier. */ 511 synchronized (this) { 512 HashMap<Integer, BluetoothLeBroadcastReceiveState> states = 513 svc_data.receiverStatesMutable.getValue(); 514 if (states == null) states = new HashMap<>(); 515 states.put(state.getSourceId(), state); 516 517 // Use SetValue instead of PostValue() since we want to make it 518 // synchronous due to getValue() we do here as well 519 // Otherwise we could miss the update and store only the last 520 // receiver ID 521 // svc_data.receiverStatesMutable.setValue(states); 522 svc_data.receiverStatesMutable.postValue(states); 523 } 524 } 525 } 526 }; 527 528 private final BroadcastReceiver bassIntentReceiver = 529 new BroadcastReceiver() { 530 @Override 531 public void onReceive(Context context, Intent intent) { 532 String action = intent.getAction(); 533 if (action.equals( 534 BluetoothLeBroadcastAssistant.ACTION_CONNECTION_STATE_CHANGED)) { 535 final BluetoothDevice device = 536 intent.getParcelableExtra( 537 BluetoothDevice.EXTRA_DEVICE, BluetoothDevice.class); 538 539 if (allLeAudioDevicesMutable.getValue() != null) { 540 if (device != null) { 541 Optional<LeAudioDeviceStateWrapper> valid_device_opt = 542 allLeAudioDevicesMutable.getValue().stream() 543 .filter( 544 state -> 545 state.device 546 .getAddress() 547 .equals( 548 device 549 .getAddress())) 550 .findAny(); 551 552 if (valid_device_opt.isPresent()) { 553 LeAudioDeviceStateWrapper valid_device = valid_device_opt.get(); 554 LeAudioDeviceStateWrapper.BassData svc_data = 555 valid_device.bassData; 556 557 final int toState = 558 intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); 559 if (toState == STATE_CONNECTED || toState == STATE_DISCONNECTED) 560 svc_data.isConnectedMutable.postValue( 561 toState == STATE_CONNECTED); 562 } 563 } 564 } 565 } 566 // TODO: Remove this if unnecessary. 567 // case 568 // BluetoothBroadcastAudioScan.ACTION_BASS_BROADCAST_ANNONCEMENT_AVAILABLE: 569 // // FIXME: Never happen since there is no valid device with this 570 // intent 571 // break; 572 } 573 }; 574 BluetoothProxy(Application application)575 private BluetoothProxy(Application application) { 576 this.application = application; 577 bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 578 579 enabledBluetoothMutable = new MutableLiveData<>(); 580 allLeAudioDevicesMutable = new MutableLiveData<>(); 581 582 mBroadcastUpdateMutableLive = new MutableLiveData<>(); 583 mBroadcastStatusMutableLive = new MutableLiveData<>(); 584 585 mBroadcastPlaybackStartedMutableLive = new MutableLiveData<>(); 586 mBroadcastPlaybackStoppedMutableLive = new MutableLiveData<>(); 587 mBroadcastAddedMutableLive = new MutableLiveData(); 588 mBroadcastRemovedMutableLive = new MutableLiveData<>(); 589 590 MutableLiveData<String> mBroadcastStatusMutableLive; 591 592 mExecutor = Executors.newSingleThreadExecutor(); 593 594 adapterIntentFilter = new IntentFilter(); 595 adapterIntentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); 596 adapterIntentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); 597 application.registerReceiver( 598 adapterIntentReceiver, adapterIntentFilter, Context.RECEIVER_EXPORTED); 599 600 bassIntentFilter = new IntentFilter(); 601 bassIntentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); 602 bassIntentFilter.addAction(BluetoothLeBroadcastAssistant.ACTION_CONNECTION_STATE_CHANGED); 603 application.registerReceiver( 604 bassIntentReceiver, bassIntentFilter, Context.RECEIVER_EXPORTED); 605 } 606 607 // Lazy constructing Singleton acquire method getBluetoothProxy(Application application)608 public static BluetoothProxy getBluetoothProxy(Application application) { 609 if (INSTANCE == null) { 610 INSTANCE = new BluetoothProxy(application); 611 } 612 return (INSTANCE); 613 } 614 initProfiles()615 public void initProfiles() { 616 if (profileListener != null) return; 617 618 hapCallback = 619 new BluetoothHapClient.Callback() { 620 @Override 621 public void onPresetSelected( 622 BluetoothDevice device, int presetIndex, int statusCode) { 623 Optional<LeAudioDeviceStateWrapper> valid_device_opt = 624 allLeAudioDevicesMutable.getValue().stream() 625 .filter( 626 state -> 627 state.device 628 .getAddress() 629 .equals(device.getAddress())) 630 .findAny(); 631 632 if (!valid_device_opt.isPresent()) return; 633 634 LeAudioDeviceStateWrapper valid_device = valid_device_opt.get(); 635 LeAudioDeviceStateWrapper.HapData svc_data = valid_device.hapData; 636 637 svc_data.hapActivePresetIndexMutable.postValue(presetIndex); 638 639 svc_data.hapStatusMutable.postValue( 640 "Preset changed to " + presetIndex + ", reason: " + statusCode); 641 } 642 643 @Override 644 public void onPresetSelectionFailed(BluetoothDevice device, int statusCode) { 645 Optional<LeAudioDeviceStateWrapper> valid_device_opt = 646 allLeAudioDevicesMutable.getValue().stream() 647 .filter( 648 state -> 649 state.device 650 .getAddress() 651 .equals(device.getAddress())) 652 .findAny(); 653 654 if (!valid_device_opt.isPresent()) return; 655 656 LeAudioDeviceStateWrapper valid_device = valid_device_opt.get(); 657 LeAudioDeviceStateWrapper.HapData svc_data = valid_device.hapData; 658 659 svc_data.hapStatusMutable.postValue( 660 "Select preset failed with status " + statusCode); 661 } 662 663 @Override 664 public void onPresetSelectionForGroupFailed(int hapGroupId, int statusCode) { 665 List<LeAudioDeviceStateWrapper> valid_devices = null; 666 if (hapGroupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) 667 valid_devices = 668 allLeAudioDevicesMutable.getValue().stream() 669 .filter( 670 state -> 671 state.leAudioData != null 672 && state.leAudioData 673 .nodeStatusMutable 674 .getValue() 675 != null 676 && state.leAudioData 677 .nodeStatusMutable 678 .getValue() 679 .first 680 .equals(hapGroupId)) 681 .collect(Collectors.toList()); 682 683 if (valid_devices != null) { 684 for (LeAudioDeviceStateWrapper device : valid_devices) { 685 device.hapData.hapStatusMutable.postValue( 686 "Select preset for group " 687 + hapGroupId 688 + " failed with status " 689 + statusCode); 690 } 691 } 692 } 693 694 @Override 695 public void onPresetInfoChanged( 696 BluetoothDevice device, 697 List<BluetoothHapPresetInfo> presetInfoList, 698 int statusCode) { 699 Optional<LeAudioDeviceStateWrapper> valid_device_opt = 700 allLeAudioDevicesMutable.getValue().stream() 701 .filter( 702 state -> 703 state.device 704 .getAddress() 705 .equals(device.getAddress())) 706 .findAny(); 707 708 if (!valid_device_opt.isPresent()) return; 709 710 LeAudioDeviceStateWrapper valid_device = valid_device_opt.get(); 711 LeAudioDeviceStateWrapper.HapData svc_data = valid_device.hapData; 712 713 svc_data.hapStatusMutable.postValue( 714 "Preset list changed due to status " + statusCode); 715 svc_data.hapPresetsMutable.postValue(presetInfoList); 716 } 717 718 @Override 719 public void onSetPresetNameFailed(BluetoothDevice device, int status) { 720 Optional<LeAudioDeviceStateWrapper> valid_device_opt = 721 allLeAudioDevicesMutable.getValue().stream() 722 .filter( 723 state -> 724 state.device 725 .getAddress() 726 .equals(device.getAddress())) 727 .findAny(); 728 729 if (!valid_device_opt.isPresent()) return; 730 731 LeAudioDeviceStateWrapper valid_device = valid_device_opt.get(); 732 LeAudioDeviceStateWrapper.HapData svc_data = valid_device.hapData; 733 734 svc_data.hapStatusMutable.postValue("Name set error: " + status); 735 } 736 737 @Override 738 public void onSetPresetNameForGroupFailed(int hapGroupId, int status) { 739 List<LeAudioDeviceStateWrapper> valid_devices = null; 740 if (hapGroupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) 741 valid_devices = 742 allLeAudioDevicesMutable.getValue().stream() 743 .filter( 744 state -> 745 state.leAudioData != null 746 && state.leAudioData 747 .nodeStatusMutable 748 .getValue() 749 != null 750 && state.leAudioData 751 .nodeStatusMutable 752 .getValue() 753 .first 754 .equals(hapGroupId)) 755 .collect(Collectors.toList()); 756 757 if (valid_devices != null) { 758 for (LeAudioDeviceStateWrapper device : valid_devices) { 759 device.hapData.hapStatusMutable.postValue( 760 "Group Name set error: " + status); 761 } 762 } 763 } 764 }; 765 766 profileListener = 767 new BluetoothProfile.ServiceListener() { 768 @Override 769 public void onServiceConnected(int i, BluetoothProfile bluetoothProfile) { 770 Log.d( 771 "BluetoothProxy", 772 "onServiceConnected(): i = " 773 + i 774 + " bluetoothProfile = " 775 + bluetoothProfile); 776 switch (i) { 777 case BluetoothProfile.CSIP_SET_COORDINATOR: 778 bluetoothCsis = (BluetoothCsipSetCoordinator) bluetoothProfile; 779 break; 780 case BluetoothProfile.LE_AUDIO: 781 bluetoothLeAudio = (BluetoothLeAudio) bluetoothProfile; 782 if (!mLeAudioCallbackRegistered) { 783 try { 784 bluetoothLeAudio.registerCallback( 785 mExecutor, mLeAudioCallbacks); 786 mLeAudioCallbackRegistered = true; 787 } catch (Exception e) { 788 Log.e( 789 "Unicast:", 790 " Probably not supported: Exception on registering" 791 + " callbacks: " 792 + e); 793 } 794 } 795 break; 796 case BluetoothProfile.VOLUME_CONTROL: 797 bluetoothVolumeControl = (BluetoothVolumeControl) bluetoothProfile; 798 break; 799 case BluetoothProfile.HAP_CLIENT: 800 bluetoothHapClient = (BluetoothHapClient) bluetoothProfile; 801 try { 802 bluetoothHapClient.registerCallback(mExecutor, hapCallback); 803 } catch (IllegalArgumentException e) { 804 Log.e("HAP", "Application callback already registered."); 805 } 806 break; 807 case BluetoothProfile.LE_AUDIO_BROADCAST: 808 mBluetoothLeBroadcast = (BluetoothLeBroadcast) bluetoothProfile; 809 try { 810 mBluetoothLeBroadcast.registerCallback( 811 mExecutor, mBroadcasterCallback); 812 } catch (IllegalArgumentException e) { 813 Log.e("Broadcast", "Application callback already registered."); 814 } 815 break; 816 case BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT: 817 Log.d( 818 "BluetoothProxy", 819 "LE_AUDIO_BROADCAST_ASSISTANT Service connected"); 820 mBluetoothLeBroadcastAssistant = 821 (BluetoothLeBroadcastAssistant) bluetoothProfile; 822 try { 823 mBluetoothLeBroadcastAssistant.registerCallback( 824 mExecutor, mBroadcastAssistantCallback); 825 } catch (IllegalArgumentException e) { 826 Log.e("BASS", "Application callback already registered."); 827 } 828 break; 829 } 830 queryLeAudioDevices(); 831 } 832 833 @Override 834 public void onServiceDisconnected(int i) {} 835 }; 836 837 initCsisProxy(); 838 initLeAudioProxy(); 839 initVolumeControlProxy(); 840 initHapProxy(); 841 initLeAudioBroadcastProxy(); 842 initBassProxy(); 843 } 844 cleanupProfiles()845 public void cleanupProfiles() { 846 if (profileListener == null) return; 847 848 cleanupCsisProxy(); 849 cleanupLeAudioProxy(); 850 cleanupVolumeControlProxy(); 851 cleanupHapProxy(); 852 cleanupLeAudioBroadcastProxy(); 853 cleanupBassProxy(); 854 855 profileListener = null; 856 } 857 initCsisProxy()858 private void initCsisProxy() { 859 if (!isCoordinatedSetProfileSupported()) return; 860 if (bluetoothCsis == null) { 861 bluetoothAdapter.getProfileProxy( 862 this.application, profileListener, BluetoothProfile.CSIP_SET_COORDINATOR); 863 } 864 } 865 cleanupCsisProxy()866 private void cleanupCsisProxy() { 867 if (!isCoordinatedSetProfileSupported()) return; 868 if (bluetoothCsis != null) { 869 bluetoothAdapter.closeProfileProxy(BluetoothProfile.LE_AUDIO, bluetoothCsis); 870 } 871 } 872 initLeAudioProxy()873 private void initLeAudioProxy() { 874 if (!isLeAudioUnicastSupported()) return; 875 if (bluetoothLeAudio == null) { 876 bluetoothAdapter.getProfileProxy( 877 this.application, profileListener, BluetoothProfile.LE_AUDIO); 878 } 879 880 intentFilter = new IntentFilter(); 881 intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); 882 intentFilter.addAction(BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED); 883 application.registerReceiver( 884 leAudioIntentReceiver, intentFilter, Context.RECEIVER_EXPORTED); 885 } 886 cleanupLeAudioProxy()887 private void cleanupLeAudioProxy() { 888 if (!isLeAudioUnicastSupported()) return; 889 if (bluetoothLeAudio != null) { 890 bluetoothAdapter.closeProfileProxy(BluetoothProfile.LE_AUDIO, bluetoothLeAudio); 891 application.unregisterReceiver(leAudioIntentReceiver); 892 } 893 } 894 initVolumeControlProxy()895 private void initVolumeControlProxy() { 896 if (!isVolumeControlClientSupported()) return; 897 bluetoothAdapter.getProfileProxy( 898 this.application, profileListener, BluetoothProfile.VOLUME_CONTROL); 899 900 intentFilter = new IntentFilter(); 901 intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); 902 intentFilter.addAction(BluetoothVolumeControl.ACTION_CONNECTION_STATE_CHANGED); 903 application.registerReceiver( 904 volumeControlIntentReceiver, intentFilter, Context.RECEIVER_EXPORTED); 905 } 906 cleanupVolumeControlProxy()907 private void cleanupVolumeControlProxy() { 908 if (!isVolumeControlClientSupported()) return; 909 if (bluetoothVolumeControl != null) { 910 bluetoothAdapter.closeProfileProxy( 911 BluetoothProfile.VOLUME_CONTROL, bluetoothVolumeControl); 912 application.unregisterReceiver(volumeControlIntentReceiver); 913 } 914 } 915 initHapProxy()916 private void initHapProxy() { 917 if (!isLeAudioHearingAccessClientSupported()) return; 918 bluetoothAdapter.getProfileProxy( 919 this.application, profileListener, BluetoothProfile.HAP_CLIENT); 920 921 intentFilter = new IntentFilter(); 922 intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); 923 intentFilter.addAction(BluetoothHapClient.ACTION_HAP_CONNECTION_STATE_CHANGED); 924 intentFilter.addAction("android.bluetooth.action.HAP_DEVICE_AVAILABLE"); 925 application.registerReceiver( 926 hapClientIntentReceiver, intentFilter, Context.RECEIVER_EXPORTED); 927 } 928 cleanupHapProxy()929 private void cleanupHapProxy() { 930 if (!isLeAudioHearingAccessClientSupported()) return; 931 if (bluetoothHapClient != null) { 932 bluetoothHapClient.unregisterCallback(hapCallback); 933 bluetoothAdapter.closeProfileProxy(BluetoothProfile.HAP_CLIENT, bluetoothHapClient); 934 application.unregisterReceiver(hapClientIntentReceiver); 935 } 936 } 937 initBassProxy()938 private void initBassProxy() { 939 if (!isLeAudioBroadcastScanAssistanSupported()) return; 940 bluetoothAdapter.getProfileProxy( 941 this.application, profileListener, BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT); 942 } 943 cleanupBassProxy()944 private void cleanupBassProxy() { 945 if (!isLeAudioBroadcastScanAssistanSupported()) return; 946 if (mBluetoothLeBroadcastAssistant != null) { 947 mBluetoothLeBroadcastAssistant.unregisterCallback(mBroadcastAssistantCallback); 948 bluetoothAdapter.closeProfileProxy( 949 BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT, mBluetoothLeBroadcastAssistant); 950 } 951 } 952 checkForEnabledBluetooth()953 private Boolean checkForEnabledBluetooth() { 954 Boolean current_state = bluetoothAdapter.isEnabled(); 955 956 // Force the update since event may not come if bt was already enabled 957 if (!Objects.equals(enabledBluetoothMutable.getValue(), current_state)) 958 enabledBluetoothMutable.setValue(current_state); 959 960 return current_state; 961 } 962 queryLeAudioDevices()963 public void queryLeAudioDevices() { 964 if (checkForEnabledBluetooth()) { 965 // Consider those with the ASC service as valid devices 966 List<LeAudioDeviceStateWrapper> validDevices = new ArrayList<>(); 967 for (BluetoothDevice dev : bluetoothAdapter.getBondedDevices()) { 968 LeAudioDeviceStateWrapper state_wrapper = new LeAudioDeviceStateWrapper(dev); 969 Boolean valid_device = false; 970 971 if (Arrays.asList(dev.getUuids() != null ? dev.getUuids() : new ParcelUuid[0]) 972 .contains( 973 ParcelUuid.fromString( 974 application.getString(R.string.svc_uuid_le_audio)))) { 975 if (state_wrapper.leAudioData == null) 976 state_wrapper.leAudioData = new LeAudioDeviceStateWrapper.LeAudioData(); 977 valid_device = true; 978 979 if (bluetoothLeAudio != null) { 980 state_wrapper.leAudioData.isConnectedMutable.postValue( 981 bluetoothLeAudio.getConnectionState(dev) 982 == BluetoothLeAudio.STATE_CONNECTED); 983 int group_id = bluetoothLeAudio.getGroupId(dev); 984 state_wrapper.leAudioData.nodeStatusMutable.setValue( 985 new Pair<>(group_id, GROUP_NODE_ADDED)); 986 state_wrapper.leAudioData.groupStatusMutable.setValue( 987 new Pair<>(group_id, new Pair<>(-1, -1))); 988 } 989 } 990 991 if (Arrays.asList(dev.getUuids() != null ? dev.getUuids() : new ParcelUuid[0]) 992 .contains( 993 ParcelUuid.fromString( 994 application.getString(R.string.svc_uuid_volume_control)))) { 995 if (state_wrapper.volumeControlData == null) 996 state_wrapper.volumeControlData = 997 new LeAudioDeviceStateWrapper.VolumeControlData(); 998 valid_device = true; 999 1000 if (bluetoothVolumeControl != null) { 1001 state_wrapper.volumeControlData.isConnectedMutable.postValue( 1002 bluetoothVolumeControl.getConnectionState(dev) 1003 == BluetoothVolumeControl.STATE_CONNECTED); 1004 // FIXME: We don't have the api to get the volume and mute states? :( 1005 } 1006 } 1007 1008 if (Arrays.asList(dev.getUuids() != null ? dev.getUuids() : new ParcelUuid[0]) 1009 .contains( 1010 ParcelUuid.fromString( 1011 application.getString(R.string.svc_uuid_has)))) { 1012 if (state_wrapper.hapData == null) 1013 state_wrapper.hapData = new LeAudioDeviceStateWrapper.HapData(); 1014 valid_device = true; 1015 1016 if (bluetoothHapClient != null) { 1017 state_wrapper.hapData.hapStateMutable.postValue( 1018 bluetoothHapClient.getConnectionState(dev)); 1019 boolean is_connected = 1020 bluetoothHapClient.getConnectionState(dev) 1021 == BluetoothHapClient.STATE_CONNECTED; 1022 if (is_connected) { 1023 // Use hidden API 1024 try { 1025 Method getFeaturesMethod = 1026 BluetoothHapClient.class.getDeclaredMethod( 1027 "getFeatures", BluetoothDevice.class); 1028 getFeaturesMethod.setAccessible(true); 1029 state_wrapper.hapData.hapFeaturesMutable.postValue( 1030 (Integer) 1031 getFeaturesMethod.invoke(bluetoothHapClient, dev)); 1032 } catch (NoSuchMethodException 1033 | IllegalAccessException 1034 | InvocationTargetException e) { 1035 state_wrapper.hapData.hapStatusMutable.postValue( 1036 "Hidden API for getFeatures not accessible."); 1037 } 1038 1039 state_wrapper.hapData.hapPresetsMutable.postValue( 1040 bluetoothHapClient.getAllPresetInfo(dev)); 1041 try { 1042 Method getActivePresetIndexMethod = 1043 BluetoothHapClient.class.getDeclaredMethod( 1044 "getActivePresetIndex", BluetoothDevice.class); 1045 getActivePresetIndexMethod.setAccessible(true); 1046 state_wrapper.hapData.hapActivePresetIndexMutable.postValue( 1047 (Integer) 1048 getActivePresetIndexMethod.invoke( 1049 bluetoothHapClient, dev)); 1050 } catch (NoSuchMethodException 1051 | IllegalAccessException 1052 | InvocationTargetException e) { 1053 state_wrapper.hapData.hapStatusMutable.postValue( 1054 "Hidden API for getFeatures not accessible."); 1055 } 1056 } 1057 } 1058 } 1059 1060 if (Arrays.asList(dev.getUuids() != null ? dev.getUuids() : new ParcelUuid[0]) 1061 .contains( 1062 ParcelUuid.fromString( 1063 application.getString( 1064 R.string.svc_uuid_broadcast_audio)))) { 1065 if (state_wrapper.bassData == null) 1066 state_wrapper.bassData = new LeAudioDeviceStateWrapper.BassData(); 1067 valid_device = true; 1068 1069 if (mBluetoothLeBroadcastAssistant != null) { 1070 boolean is_connected = 1071 mBluetoothLeBroadcastAssistant.getConnectionState(dev) 1072 == STATE_CONNECTED; 1073 state_wrapper.bassData.isConnectedMutable.setValue(is_connected); 1074 } 1075 } 1076 1077 if (valid_device) validDevices.add(state_wrapper); 1078 } 1079 1080 // Async update 1081 allLeAudioDevicesMutable.postValue(validDevices); 1082 } 1083 } 1084 connectLeAudio(BluetoothDevice device, boolean connect)1085 public void connectLeAudio(BluetoothDevice device, boolean connect) { 1086 if (bluetoothLeAudio != null) { 1087 if (connect) { 1088 try { 1089 Method connectMethod = 1090 BluetoothLeAudio.class.getDeclaredMethod( 1091 "connect", BluetoothDevice.class); 1092 connectMethod.setAccessible(true); 1093 connectMethod.invoke(bluetoothLeAudio, device); 1094 } catch (NoSuchMethodException 1095 | IllegalAccessException 1096 | InvocationTargetException e) { 1097 // Do nothing 1098 } 1099 } else { 1100 try { 1101 Method disconnectMethod = 1102 BluetoothLeAudio.class.getDeclaredMethod( 1103 "disconnect", BluetoothDevice.class); 1104 disconnectMethod.setAccessible(true); 1105 disconnectMethod.invoke(bluetoothLeAudio, device); 1106 } catch (NoSuchMethodException 1107 | IllegalAccessException 1108 | InvocationTargetException e) { 1109 // Do nothing 1110 } 1111 } 1112 } 1113 } 1114 streamAction(Integer group_id, int action, Integer content_type)1115 public void streamAction(Integer group_id, int action, Integer content_type) { 1116 if (bluetoothLeAudio != null) { 1117 switch (action) { 1118 case 0: 1119 // No longer available, not needed 1120 // bluetoothLeAudio.groupStream(group_id, content_type); 1121 break; 1122 case 1: 1123 // No longer available, not needed 1124 // bluetoothLeAudio.groupSuspend(group_id); 1125 break; 1126 case 2: 1127 // No longer available, not needed 1128 // bluetoothLeAudio.groupStop(group_id); 1129 break; 1130 default: 1131 break; 1132 } 1133 } 1134 } 1135 groupSet(BluetoothDevice device, Integer group_id)1136 public void groupSet(BluetoothDevice device, Integer group_id) { 1137 if (bluetoothLeAudio == null) return; 1138 1139 try { 1140 Method groupAddNodeMethod = 1141 BluetoothLeAudio.class.getDeclaredMethod( 1142 "groupAddNode", int.class, BluetoothDevice.class); 1143 groupAddNodeMethod.setAccessible(true); 1144 groupAddNodeMethod.invoke(bluetoothLeAudio, group_id, device); 1145 } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { 1146 // Do nothing 1147 } 1148 } 1149 groupUnset(BluetoothDevice device, Integer group_id)1150 public void groupUnset(BluetoothDevice device, Integer group_id) { 1151 if (bluetoothLeAudio == null) return; 1152 1153 try { 1154 Method groupRemoveNodeMethod = 1155 BluetoothLeAudio.class.getDeclaredMethod( 1156 "groupRemoveNode", int.class, BluetoothDevice.class); 1157 groupRemoveNodeMethod.setAccessible(true); 1158 groupRemoveNodeMethod.invoke(bluetoothLeAudio, group_id, device); 1159 } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { 1160 // Do nothing 1161 } 1162 } 1163 groupSetLock(Integer group_id, boolean lock)1164 public void groupSetLock(Integer group_id, boolean lock) { 1165 if (bluetoothCsis == null) return; 1166 1167 Log.d("Lock", "lock: " + lock); 1168 if (lock) { 1169 if (mGroupLocks.containsKey(group_id)) { 1170 Log.e( 1171 "Lock", 1172 "group" + group_id + " is already in locking process or locked: " + lock); 1173 return; 1174 } 1175 1176 UUID uuid = 1177 bluetoothCsis.lockGroup( 1178 group_id, 1179 mExecutor, 1180 (int group, int op_status, boolean is_locked) -> { 1181 Log.d("LockCb", "lock: " + is_locked + " status: " + op_status); 1182 if (((op_status == BluetoothStatusCodes.SUCCESS) 1183 || (op_status 1184 == BluetoothStatusCodes 1185 .ERROR_CSIP_LOCKED_GROUP_MEMBER_LOST)) 1186 && (group != BluetoothLeAudio.GROUP_ID_INVALID)) { 1187 allLeAudioDevicesMutable 1188 .getValue() 1189 .forEach( 1190 (dev_wrapper) -> { 1191 if (dev_wrapper.leAudioData 1192 .nodeStatusMutable 1193 .getValue() 1194 != null 1195 && dev_wrapper 1196 .leAudioData 1197 .nodeStatusMutable 1198 .getValue() 1199 .first 1200 .equals(group_id)) { 1201 dev_wrapper.leAudioData 1202 .groupLockStateMutable 1203 .postValue( 1204 new Pair< 1205 Integer, 1206 Boolean>( 1207 group, 1208 is_locked)); 1209 } 1210 }); 1211 } else { 1212 // TODO: Set error status so it could be notified/toasted to the 1213 // user 1214 } 1215 1216 if (!is_locked) mGroupLocks.remove(group_id); 1217 }); 1218 // Store the lock key 1219 mGroupLocks.put(group_id, uuid); 1220 } else { 1221 if (!mGroupLocks.containsKey(group_id)) return; 1222 1223 // Use the stored lock key 1224 bluetoothCsis.unlockGroup(mGroupLocks.get(group_id)); 1225 mGroupLocks.remove(group_id); 1226 } 1227 } 1228 connectBass(BluetoothDevice device, boolean connect)1229 public void connectBass(BluetoothDevice device, boolean connect) { 1230 if (mBluetoothLeBroadcastAssistant != null) { 1231 if (connect) { 1232 mBluetoothLeBroadcastAssistant.setConnectionPolicy( 1233 device, CONNECTION_POLICY_ALLOWED); 1234 } else { 1235 mBluetoothLeBroadcastAssistant.setConnectionPolicy( 1236 device, CONNECTION_POLICY_FORBIDDEN); 1237 } 1238 } 1239 } 1240 scanForBroadcasts(@ullable BluetoothDevice scanDelegator, boolean scan)1241 public boolean scanForBroadcasts(@Nullable BluetoothDevice scanDelegator, boolean scan) { 1242 if (mBluetoothLeBroadcastAssistant != null) { 1243 // Note: startSearchingForSources() does not support scanning on behalf of 1244 // a specific device - it only searches for all BASS connected devices. 1245 // Therefore, we manage the list of the devices and start/stop the scanning. 1246 if (scan) { 1247 if (scanDelegator != null) { 1248 mBroadcastScanDelegatorDevices.add(scanDelegator); 1249 } 1250 try { 1251 mBluetoothLeBroadcastAssistant.startSearchingForSources(new ArrayList<>()); 1252 } catch (IllegalArgumentException e) { 1253 Log.e("BluetoothProxy", " Unexpected " + e); 1254 } 1255 if (mBassEventListener != null) { 1256 mBassEventListener.onScanningStateChanged(true); 1257 } 1258 } else { 1259 if (scanDelegator != null) { 1260 mBroadcastScanDelegatorDevices.remove(scanDelegator); 1261 } 1262 if (mBroadcastScanDelegatorDevices.isEmpty()) { 1263 try { 1264 mBluetoothLeBroadcastAssistant.stopSearchingForSources(); 1265 if (mBassEventListener != null) { 1266 mBassEventListener.onScanningStateChanged(false); 1267 } 1268 } catch (IllegalArgumentException e) { 1269 Log.e("BluetoothProxy", " Unexpected " + e); 1270 } 1271 } 1272 } 1273 return true; 1274 } 1275 return false; 1276 } 1277 stopBroadcastObserving()1278 public boolean stopBroadcastObserving() { 1279 if (mBluetoothLeBroadcastAssistant != null) { 1280 mBroadcastScanDelegatorDevices.clear(); 1281 try { 1282 mBluetoothLeBroadcastAssistant.stopSearchingForSources(); 1283 } catch (IllegalArgumentException e) { 1284 Log.e("BluetoothProxy", " Unexpected " + e); 1285 } 1286 1287 if (mBassEventListener != null) { 1288 mBassEventListener.onScanningStateChanged(false); 1289 } 1290 return true; 1291 } 1292 return false; 1293 } 1294 1295 // TODO: Uncomment this method if necessary 1296 // public boolean getBroadcastReceiverState(BluetoothDevice device, int receiver_id) { 1297 // if (mBluetoothLeBroadcastAssistant != null) { 1298 // return mBluetoothLeBroadcastAssistant.getBroadcastReceiverState(device, 1299 // receiver_id); 1300 // } 1301 // return false; 1302 // } 1303 addBroadcastSource( BluetoothDevice sink, BluetoothLeBroadcastMetadata sourceMetadata)1304 public boolean addBroadcastSource( 1305 BluetoothDevice sink, BluetoothLeBroadcastMetadata sourceMetadata) { 1306 if (mBluetoothLeBroadcastAssistant != null) { 1307 mBluetoothLeBroadcastAssistant.addSource(sink, sourceMetadata, true /* isGroupOp */); 1308 return true; 1309 } 1310 return false; 1311 } 1312 modifyBroadcastSource( BluetoothDevice sink, int sourceId, BluetoothLeBroadcastMetadata metadata)1313 public boolean modifyBroadcastSource( 1314 BluetoothDevice sink, int sourceId, BluetoothLeBroadcastMetadata metadata) { 1315 if (mBluetoothLeBroadcastAssistant != null) { 1316 mBluetoothLeBroadcastAssistant.modifySource(sink, sourceId, metadata); 1317 return true; 1318 } 1319 return false; 1320 } 1321 removeBroadcastSource(BluetoothDevice sink, int sourceId)1322 public boolean removeBroadcastSource(BluetoothDevice sink, int sourceId) { 1323 if (mBluetoothLeBroadcastAssistant != null) { 1324 mBluetoothLeBroadcastAssistant.removeSource(sink, sourceId); 1325 return true; 1326 } 1327 return false; 1328 } 1329 setVolume(BluetoothDevice device, int volume)1330 public void setVolume(BluetoothDevice device, int volume) { 1331 if (bluetoothLeAudio != null && !bluetoothLeAudio.getConnectedDevices().isEmpty()) { 1332 bluetoothLeAudio.setVolume(volume); 1333 } else if (bluetoothVolumeControl != null) { 1334 bluetoothVolumeControl.setVolumeOffset(device, volume); 1335 } 1336 } 1337 getBluetoothEnabled()1338 public LiveData<Boolean> getBluetoothEnabled() { 1339 return enabledBluetoothMutable; 1340 } 1341 getAllLeAudioDevices()1342 public LiveData<List<LeAudioDeviceStateWrapper>> getAllLeAudioDevices() { 1343 return allLeAudioDevicesMutable; 1344 } 1345 connectHap(BluetoothDevice device, boolean connect)1346 public void connectHap(BluetoothDevice device, boolean connect) { 1347 if (bluetoothHapClient != null) { 1348 if (connect) { 1349 bluetoothHapClient.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); 1350 } else { 1351 bluetoothHapClient.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); 1352 } 1353 } 1354 } 1355 connectGattBr( Context context, LeAudioDeviceStateWrapper device_wrapper, boolean connect)1356 public void connectGattBr( 1357 Context context, LeAudioDeviceStateWrapper device_wrapper, boolean connect) { 1358 1359 BluetoothGatt bluetoothGatt = bluetoothGattMap.get(device_wrapper); 1360 if (bluetoothGatt == null) { 1361 bluetoothGatt = 1362 device_wrapper.device.connectGatt( 1363 context, 1364 false, 1365 new BluetoothGattCallback() { 1366 public void onConnectionStateChange( 1367 BluetoothGatt gatt, int status, int newState) { 1368 LeAudioDeviceStateWrapper device_wrapper = null; 1369 for (Map.Entry<LeAudioDeviceStateWrapper, BluetoothGatt> entry : 1370 bluetoothGattMap.entrySet()) { 1371 if (gatt == entry.getValue()) { 1372 device_wrapper = entry.getKey(); 1373 break; 1374 } 1375 } 1376 if (device_wrapper == null) { 1377 return; 1378 } 1379 1380 switch (newState) { 1381 case STATE_DISCONNECTED: 1382 device_wrapper.isGattBrConnectedMutable.postValue( 1383 false); 1384 break; 1385 case STATE_CONNECTED: 1386 device_wrapper.isGattBrConnectedMutable.postValue(true); 1387 break; 1388 default: 1389 break; 1390 } 1391 } 1392 }, 1393 BluetoothDevice.TRANSPORT_BREDR); 1394 bluetoothGattMap.put(device_wrapper, bluetoothGatt); 1395 } 1396 1397 if (bluetoothGatt == null) { 1398 return; 1399 } 1400 1401 if (connect) { 1402 bluetoothGatt.connect(); 1403 } else { 1404 bluetoothGatt.disconnect(); 1405 } 1406 } 1407 hapReadPresetInfo(BluetoothDevice device, int preset_index)1408 public boolean hapReadPresetInfo(BluetoothDevice device, int preset_index) { 1409 if (bluetoothHapClient == null) return false; 1410 1411 BluetoothHapPresetInfo new_preset = null; 1412 1413 // Use hidden API 1414 try { 1415 Method getPresetInfoMethod = 1416 BluetoothHapClient.class.getDeclaredMethod( 1417 "getPresetInfo", BluetoothDevice.class, int.class); 1418 getPresetInfoMethod.setAccessible(true); 1419 1420 new_preset = 1421 (BluetoothHapPresetInfo) 1422 getPresetInfoMethod.invoke(bluetoothHapClient, device, preset_index); 1423 if (new_preset == null) return false; 1424 } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { 1425 // Do nothing' 1426 return false; 1427 } 1428 1429 Optional<LeAudioDeviceStateWrapper> valid_device_opt = 1430 allLeAudioDevicesMutable.getValue().stream() 1431 .filter(state -> state.device.getAddress().equals(device.getAddress())) 1432 .findAny(); 1433 1434 if (!valid_device_opt.isPresent()) return false; 1435 1436 LeAudioDeviceStateWrapper valid_device = valid_device_opt.get(); 1437 LeAudioDeviceStateWrapper.HapData svc_data = valid_device.hapData; 1438 1439 List current_presets = svc_data.hapPresetsMutable.getValue(); 1440 if (current_presets == null) current_presets = new ArrayList<BluetoothHapPresetInfo>(); 1441 1442 // Remove old one and add back the new one 1443 ListIterator<BluetoothHapPresetInfo> iter = current_presets.listIterator(); 1444 while (iter.hasNext()) { 1445 if (iter.next().getIndex() == new_preset.getIndex()) { 1446 iter.remove(); 1447 } 1448 } 1449 current_presets.add(new_preset); 1450 1451 svc_data.hapPresetsMutable.postValue(current_presets); 1452 return true; 1453 } 1454 hapSetActivePreset(BluetoothDevice device, int preset_index)1455 public boolean hapSetActivePreset(BluetoothDevice device, int preset_index) { 1456 if (bluetoothHapClient == null) return false; 1457 1458 bluetoothHapClient.selectPreset(device, preset_index); 1459 return true; 1460 } 1461 hapSetActivePresetForGroup(BluetoothDevice device, int preset_index)1462 public boolean hapSetActivePresetForGroup(BluetoothDevice device, int preset_index) { 1463 if (bluetoothHapClient == null) return false; 1464 1465 int groupId = bluetoothLeAudio.getGroupId(device); 1466 bluetoothHapClient.selectPresetForGroup(groupId, preset_index); 1467 return true; 1468 } 1469 hapChangePresetName(BluetoothDevice device, int preset_index, String name)1470 public boolean hapChangePresetName(BluetoothDevice device, int preset_index, String name) { 1471 if (bluetoothHapClient == null) return false; 1472 1473 bluetoothHapClient.setPresetName(device, preset_index, name); 1474 return true; 1475 } 1476 hapPreviousDevicePreset(BluetoothDevice device)1477 public boolean hapPreviousDevicePreset(BluetoothDevice device) { 1478 if (bluetoothHapClient == null) return false; 1479 1480 // Use hidden API 1481 try { 1482 Method switchToPreviousPresetMethod = 1483 BluetoothHapClient.class.getDeclaredMethod( 1484 "switchToPreviousPreset", BluetoothDevice.class); 1485 switchToPreviousPresetMethod.setAccessible(true); 1486 1487 switchToPreviousPresetMethod.invoke(bluetoothHapClient, device); 1488 } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { 1489 return false; 1490 } 1491 return true; 1492 } 1493 hapNextDevicePreset(BluetoothDevice device)1494 public boolean hapNextDevicePreset(BluetoothDevice device) { 1495 if (bluetoothHapClient == null) return false; 1496 1497 // Use hidden API 1498 try { 1499 Method switchToNextPresetMethod = 1500 BluetoothHapClient.class.getDeclaredMethod( 1501 "switchToNextPreset", BluetoothDevice.class); 1502 switchToNextPresetMethod.setAccessible(true); 1503 1504 switchToNextPresetMethod.invoke(bluetoothHapClient, device); 1505 } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { 1506 return false; 1507 } 1508 return true; 1509 } 1510 hapPreviousGroupPreset(int group_id)1511 public boolean hapPreviousGroupPreset(int group_id) { 1512 if (bluetoothHapClient == null) return false; 1513 1514 // Use hidden API 1515 try { 1516 Method switchToPreviousPresetForGroupMethod = 1517 BluetoothHapClient.class.getDeclaredMethod( 1518 "switchToPreviousPresetForGroup", int.class); 1519 switchToPreviousPresetForGroupMethod.setAccessible(true); 1520 1521 switchToPreviousPresetForGroupMethod.invoke(bluetoothHapClient, group_id); 1522 } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { 1523 return false; 1524 } 1525 return true; 1526 } 1527 hapNextGroupPreset(int group_id)1528 public boolean hapNextGroupPreset(int group_id) { 1529 if (bluetoothHapClient == null) return false; 1530 1531 // Use hidden API 1532 try { 1533 Method switchToNextPresetForGroupMethod = 1534 BluetoothHapClient.class.getDeclaredMethod( 1535 "switchToNextPresetForGroup", int.class); 1536 switchToNextPresetForGroupMethod.setAccessible(true); 1537 1538 switchToNextPresetForGroupMethod.invoke(bluetoothHapClient, group_id); 1539 } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { 1540 return false; 1541 } 1542 return true; 1543 } 1544 hapGetHapGroup(BluetoothDevice device)1545 public int hapGetHapGroup(BluetoothDevice device) { 1546 if (bluetoothHapClient == null) return BluetoothCsipSetCoordinator.GROUP_ID_INVALID; 1547 1548 // Use hidden API 1549 try { 1550 Method getHapGroupMethod = 1551 BluetoothHapClient.class.getDeclaredMethod( 1552 "getHapGroup", BluetoothDevice.class); 1553 getHapGroupMethod.setAccessible(true); 1554 1555 return (Integer) getHapGroupMethod.invoke(bluetoothHapClient, device); 1556 } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { 1557 // Do nothing 1558 } 1559 return BluetoothCsipSetCoordinator.GROUP_ID_INVALID; 1560 } 1561 initLeAudioBroadcastProxy()1562 private void initLeAudioBroadcastProxy() { 1563 if (!isLeAudioBroadcastSourceSupported()) return; 1564 if (mBluetoothLeBroadcast == null) { 1565 bluetoothAdapter.getProfileProxy( 1566 this.application, profileListener, BluetoothProfile.LE_AUDIO_BROADCAST); 1567 } 1568 } 1569 cleanupLeAudioBroadcastProxy()1570 private void cleanupLeAudioBroadcastProxy() { 1571 if (!isLeAudioBroadcastSourceSupported()) return; 1572 if (mBluetoothLeBroadcast != null) { 1573 bluetoothAdapter.closeProfileProxy( 1574 BluetoothProfile.LE_AUDIO_BROADCAST, mBluetoothLeBroadcast); 1575 } 1576 } 1577 getBroadcastUpdateMetadataLive()1578 public LiveData<BluetoothLeBroadcastMetadata> getBroadcastUpdateMetadataLive() { 1579 return mBroadcastUpdateMutableLive; 1580 } 1581 1582 public LiveData<Pair<Integer /* reason */, Integer /* broadcastId */>> getBroadcastPlaybackStartedMutableLive()1583 getBroadcastPlaybackStartedMutableLive() { 1584 return mBroadcastPlaybackStartedMutableLive; 1585 } 1586 1587 public LiveData<Pair<Integer /* reason */, Integer /* broadcastId */>> getBroadcastPlaybackStoppedMutableLive()1588 getBroadcastPlaybackStoppedMutableLive() { 1589 return mBroadcastPlaybackStoppedMutableLive; 1590 } 1591 getBroadcastAddedMutableLive()1592 public LiveData<Integer /* broadcastId */> getBroadcastAddedMutableLive() { 1593 return mBroadcastAddedMutableLive; 1594 } 1595 1596 public LiveData<Pair<Integer /* reason */, Integer /* broadcastId */>> getBroadcastRemovedMutableLive()1597 getBroadcastRemovedMutableLive() { 1598 return mBroadcastRemovedMutableLive; 1599 } 1600 getBroadcastStatusMutableLive()1601 public LiveData<String> getBroadcastStatusMutableLive() { 1602 return mBroadcastStatusMutableLive; 1603 } 1604 startBroadcast(BluetoothLeBroadcastSettings settings)1605 public boolean startBroadcast(BluetoothLeBroadcastSettings settings) { 1606 if (mBluetoothLeBroadcast == null) return false; 1607 mBluetoothLeBroadcast.startBroadcast(settings); 1608 return true; 1609 } 1610 stopBroadcast(int broadcastId)1611 public boolean stopBroadcast(int broadcastId) { 1612 if (mBluetoothLeBroadcast == null) return false; 1613 mBluetoothLeBroadcast.stopBroadcast(broadcastId); 1614 return true; 1615 } 1616 getAllLocalBroadcasts()1617 public List<BluetoothLeBroadcastMetadata> getAllLocalBroadcasts() { 1618 if (mBluetoothLeBroadcast == null) return Collections.emptyList(); 1619 return mBluetoothLeBroadcast.getAllBroadcastMetadata(); 1620 } 1621 updateBroadcast(int broadcastId, BluetoothLeBroadcastSettings settings)1622 public boolean updateBroadcast(int broadcastId, BluetoothLeBroadcastSettings settings) { 1623 if (mBluetoothLeBroadcast == null) return false; 1624 1625 mBluetoothLeBroadcast.updateBroadcast(broadcastId, settings); 1626 return true; 1627 } 1628 getMaximumNumberOfBroadcast()1629 public int getMaximumNumberOfBroadcast() { 1630 if (mBluetoothLeBroadcast == null) { 1631 Log.d("BluetoothProxy", "mBluetoothLeBroadcast is null"); 1632 return 0; 1633 } 1634 return mBluetoothLeBroadcast.getMaximumNumberOfBroadcasts(); 1635 } 1636 isPlaying(int broadcastId)1637 public boolean isPlaying(int broadcastId) { 1638 if (mBluetoothLeBroadcast == null) return false; 1639 return mBluetoothLeBroadcast.isPlaying(broadcastId); 1640 } 1641 isLeAudioUnicastSupported()1642 boolean isLeAudioUnicastSupported() { 1643 return (bluetoothAdapter.isLeAudioSupported() == BluetoothStatusCodes.FEATURE_SUPPORTED); 1644 } 1645 isCoordinatedSetProfileSupported()1646 boolean isCoordinatedSetProfileSupported() { 1647 return isLeAudioUnicastSupported(); 1648 } 1649 isVolumeControlClientSupported()1650 boolean isVolumeControlClientSupported() { 1651 return isLeAudioUnicastSupported(); 1652 } 1653 isLeAudioHearingAccessClientSupported()1654 boolean isLeAudioHearingAccessClientSupported() { 1655 return isLeAudioUnicastSupported(); 1656 } 1657 isLeAudioBroadcastSourceSupported()1658 public boolean isLeAudioBroadcastSourceSupported() { 1659 return (bluetoothAdapter.isLeAudioBroadcastSourceSupported() 1660 == BluetoothStatusCodes.FEATURE_SUPPORTED); 1661 } 1662 isLeAudioBroadcastScanAssistanSupported()1663 public boolean isLeAudioBroadcastScanAssistanSupported() { 1664 return (bluetoothAdapter.isLeAudioBroadcastAssistantSupported() 1665 == BluetoothStatusCodes.FEATURE_SUPPORTED); 1666 } 1667 setOnBassEventListener(OnBassEventListener listener)1668 public void setOnBassEventListener(OnBassEventListener listener) { 1669 mBassEventListener = listener; 1670 } 1671 1672 // Used by BroadcastScanViewModel 1673 public interface OnBassEventListener { onSourceFound(BluetoothLeBroadcastMetadata source)1674 void onSourceFound(BluetoothLeBroadcastMetadata source); 1675 onScanningStateChanged(boolean isScanning)1676 void onScanningStateChanged(boolean isScanning); 1677 } 1678 setOnLocalBroadcastEventListener(OnLocalBroadcastEventListener listener)1679 public void setOnLocalBroadcastEventListener(OnLocalBroadcastEventListener listener) { 1680 mLocalBroadcastEventListener = listener; 1681 } 1682 1683 // Used by BroadcastScanViewModel 1684 public interface OnLocalBroadcastEventListener { 1685 // TODO: Add arguments in methods onBroadcastStarted(int broadcastId)1686 void onBroadcastStarted(int broadcastId); 1687 onBroadcastStopped(int broadcastId)1688 void onBroadcastStopped(int broadcastId); 1689 onBroadcastUpdated(int broadcastId)1690 void onBroadcastUpdated(int broadcastId); 1691 onBroadcastMetadataChanged(int broadcastId, BluetoothLeBroadcastMetadata metadata)1692 void onBroadcastMetadataChanged(int broadcastId, BluetoothLeBroadcastMetadata metadata); 1693 } 1694 } 1695