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