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