1 /* 2 * Copyright 2020 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.le_audio; 19 20 import static android.bluetooth.IBluetoothLeAudio.LE_AUDIO_GROUP_ID_INVALID; 21 22 import android.annotation.RequiresPermission; 23 import android.annotation.SuppressLint; 24 import android.bluetooth.BluetoothDevice; 25 import android.bluetooth.BluetoothLeAudio; 26 import android.bluetooth.BluetoothProfile; 27 import android.bluetooth.BluetoothUuid; 28 import android.bluetooth.IBluetoothLeAudio; 29 import android.content.Attributable; 30 import android.content.AttributionSource; 31 import android.content.BroadcastReceiver; 32 import android.content.Context; 33 import android.content.Intent; 34 import android.content.IntentFilter; 35 import android.media.AudioManager; 36 import android.os.HandlerThread; 37 import android.os.ParcelUuid; 38 import android.util.Log; 39 40 import com.android.bluetooth.Utils; 41 import com.android.bluetooth.btservice.AdapterService; 42 import com.android.bluetooth.btservice.ProfileService; 43 import com.android.bluetooth.btservice.storage.DatabaseManager; 44 import com.android.internal.annotations.VisibleForTesting; 45 import com.android.internal.util.ArrayUtils; 46 47 import java.util.ArrayList; 48 import java.util.HashMap; 49 import java.util.List; 50 import java.util.Map; 51 import java.util.Objects; 52 import java.util.concurrent.ConcurrentHashMap; 53 54 /** 55 * Provides Bluetooth LeAudio profile, as a service in the Bluetooth application. 56 * @hide 57 */ 58 public class LeAudioService extends ProfileService { 59 private static final boolean DBG = true; 60 private static final String TAG = "LeAudioService"; 61 62 // Upper limit of all LeAudio devices: Bonded or Connected 63 private static final int MAX_LE_AUDIO_STATE_MACHINES = 10; 64 private static LeAudioService sLeAudioService; 65 66 private AdapterService mAdapterService; 67 private DatabaseManager mDatabaseManager; 68 private HandlerThread mStateMachinesThread; 69 private BluetoothDevice mPreviousAudioDevice; 70 71 LeAudioNativeInterface mLeAudioNativeInterface; 72 AudioManager mAudioManager; 73 74 private final Map<BluetoothDevice, LeAudioStateMachine> mStateMachines = new HashMap<>(); 75 76 private final Map<BluetoothDevice, Integer> mDeviceGroupIdMap = new ConcurrentHashMap<>(); 77 private final Map<Integer, Boolean> mGroupIdConnectedMap = new HashMap<>(); 78 private int mActiveDeviceGroupId = LE_AUDIO_GROUP_ID_INVALID; 79 80 private BroadcastReceiver mBondStateChangedReceiver; 81 private BroadcastReceiver mConnectionStateChangedReceiver; 82 83 @Override initBinder()84 protected IProfileServiceBinder initBinder() { 85 return new BluetoothLeAudioBinder(this); 86 } 87 88 @Override create()89 protected void create() { 90 Log.i(TAG, "create()"); 91 } 92 93 @Override start()94 protected boolean start() { 95 Log.i(TAG, "start()"); 96 if (sLeAudioService != null) { 97 throw new IllegalStateException("start() called twice"); 98 } 99 100 mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(), 101 "AdapterService cannot be null when LeAudioService starts"); 102 mLeAudioNativeInterface = Objects.requireNonNull(LeAudioNativeInterface.getInstance(), 103 "LeAudioNativeInterface cannot be null when LeAudioService starts"); 104 mDatabaseManager = Objects.requireNonNull(mAdapterService.getDatabase(), 105 "DatabaseManager cannot be null when A2dpService starts"); 106 mAudioManager = getSystemService(AudioManager.class); 107 Objects.requireNonNull(mAudioManager, 108 "AudioManager cannot be null when LeAudioService starts"); 109 110 // Start handler thread for state machines 111 mStateMachines.clear(); 112 mStateMachinesThread = new HandlerThread("LeAudioService.StateMachines"); 113 mStateMachinesThread.start(); 114 115 mDeviceGroupIdMap.clear(); 116 mGroupIdConnectedMap.clear(); 117 118 // Setup broadcast receivers 119 IntentFilter filter = new IntentFilter(); 120 filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); 121 mBondStateChangedReceiver = new BondStateChangedReceiver(); 122 registerReceiver(mBondStateChangedReceiver, filter); 123 filter = new IntentFilter(); 124 filter.addAction(BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED); 125 mConnectionStateChangedReceiver = new ConnectionStateChangedReceiver(); 126 registerReceiver(mConnectionStateChangedReceiver, filter); 127 128 // Mark service as started 129 setLeAudioService(this); 130 131 mLeAudioNativeInterface.init(); 132 133 return true; 134 } 135 136 @Override stop()137 protected boolean stop() { 138 Log.i(TAG, "stop()"); 139 if (sLeAudioService == null) { 140 Log.w(TAG, "stop() called before start()"); 141 return true; 142 } 143 144 // Cleanup native interfaces 145 mLeAudioNativeInterface.cleanup(); 146 mLeAudioNativeInterface = null; 147 148 // Set the service and BLE devices as inactive 149 setLeAudioService(null); 150 151 // Unregister broadcast receivers 152 unregisterReceiver(mBondStateChangedReceiver); 153 mBondStateChangedReceiver = null; 154 unregisterReceiver(mConnectionStateChangedReceiver); 155 mConnectionStateChangedReceiver = null; 156 157 // Destroy state machines and stop handler thread 158 synchronized (mStateMachines) { 159 for (LeAudioStateMachine sm : mStateMachines.values()) { 160 sm.doQuit(); 161 sm.cleanup(); 162 } 163 mStateMachines.clear(); 164 } 165 166 mDeviceGroupIdMap.clear(); 167 mGroupIdConnectedMap.clear(); 168 169 if (mStateMachinesThread != null) { 170 mStateMachinesThread.quitSafely(); 171 mStateMachinesThread = null; 172 } 173 174 mAudioManager = null; 175 mAdapterService = null; 176 return true; 177 } 178 179 @Override cleanup()180 protected void cleanup() { 181 Log.i(TAG, "cleanup()"); 182 } 183 getLeAudioService()184 public static synchronized LeAudioService getLeAudioService() { 185 if (sLeAudioService == null) { 186 Log.w(TAG, "getLeAudioService(): service is NULL"); 187 return null; 188 } 189 if (!sLeAudioService.isAvailable()) { 190 Log.w(TAG, "getLeAudioService(): service is not available"); 191 return null; 192 } 193 return sLeAudioService; 194 } 195 setLeAudioService(LeAudioService instance)196 private static synchronized void setLeAudioService(LeAudioService instance) { 197 if (DBG) { 198 Log.d(TAG, "setLeAudioService(): set to: " + instance); 199 } 200 sLeAudioService = instance; 201 } 202 connect(BluetoothDevice device)203 public boolean connect(BluetoothDevice device) { 204 if (DBG) { 205 Log.d(TAG, "connect(): " + device); 206 } 207 208 if (getConnectionPolicy(device) == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) { 209 Log.e(TAG, "Cannot connect to " + device + " : CONNECTION_POLICY_FORBIDDEN"); 210 return false; 211 } 212 ParcelUuid[] featureUuids = mAdapterService.getRemoteUuids(device); 213 if (!ArrayUtils.contains(featureUuids, BluetoothUuid.LE_AUDIO)) { 214 Log.e(TAG, "Cannot connect to " + device + " : Remote does not have LE_AUDIO UUID"); 215 return false; 216 } 217 218 int groupId = getGroupId(device); 219 220 //TODO: disconnect active device if it's not in groupId 221 222 if (DBG) { 223 Log.d(TAG, "connect(): " + device + "group id: " + groupId); 224 } 225 226 synchronized (mStateMachines) { 227 LeAudioStateMachine sm = getOrCreateStateMachine(device); 228 if (sm == null) { 229 Log.e(TAG, "Ignored connect request for " + device + " : no state machine"); 230 return false; 231 } 232 sm.sendMessage(LeAudioStateMachine.CONNECT, groupId); 233 } 234 235 // Connect other devices from this group 236 if (groupId != LE_AUDIO_GROUP_ID_INVALID) { 237 for (BluetoothDevice storedDevice : mDeviceGroupIdMap.keySet()) { 238 if (device.equals(storedDevice)) { 239 continue; 240 } 241 if (getGroupId(storedDevice) != groupId) { 242 continue; 243 } 244 synchronized (mStateMachines) { 245 LeAudioStateMachine sm = getOrCreateStateMachine(storedDevice); 246 if (sm == null) { 247 Log.e(TAG, "Ignored connect request for " + storedDevice 248 + " : no state machine"); 249 continue; 250 } 251 sm.sendMessage(LeAudioStateMachine.CONNECT, groupId); 252 } 253 } 254 } 255 return true; 256 } 257 258 /** 259 * Disconnects LE Audio for the remote bluetooth device 260 * 261 * @param device is the device with which we would like to disconnect LE Audio 262 * @return true if profile disconnected, false if device not connected over LE Audio 263 */ disconnect(BluetoothDevice device)264 public boolean disconnect(BluetoothDevice device) { 265 if (DBG) { 266 Log.d(TAG, "disconnect(): " + device); 267 } 268 269 // Disconnect this device 270 synchronized (mStateMachines) { 271 LeAudioStateMachine sm = mStateMachines.get(device); 272 if (sm == null) { 273 Log.e(TAG, "Ignored disconnect request for " + device 274 + " : no state machine"); 275 return false; 276 } 277 sm.sendMessage(LeAudioStateMachine.DISCONNECT); 278 } 279 280 // Disconnect other devices from this group 281 int groupId = getGroupId(device); 282 if (groupId != LE_AUDIO_GROUP_ID_INVALID) { 283 for (BluetoothDevice storedDevice : mDeviceGroupIdMap.keySet()) { 284 if (device.equals(storedDevice)) { 285 continue; 286 } 287 if (getGroupId(storedDevice) != groupId) { 288 continue; 289 } 290 synchronized (mStateMachines) { 291 LeAudioStateMachine sm = mStateMachines.get(storedDevice); 292 if (sm == null) { 293 Log.e(TAG, "Ignored disconnect request for " + storedDevice 294 + " : no state machine"); 295 continue; 296 } 297 sm.sendMessage(LeAudioStateMachine.DISCONNECT); 298 } 299 } 300 } 301 return true; 302 } 303 getConnectedDevices()304 List<BluetoothDevice> getConnectedDevices() { 305 synchronized (mStateMachines) { 306 List<BluetoothDevice> devices = new ArrayList<>(); 307 for (LeAudioStateMachine sm : mStateMachines.values()) { 308 if (sm.isConnected()) { 309 devices.add(sm.getDevice()); 310 } 311 } 312 return devices; 313 } 314 } 315 getDevicesMatchingConnectionStates(int[] states)316 List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 317 ArrayList<BluetoothDevice> devices = new ArrayList<>(); 318 if (states == null) { 319 return devices; 320 } 321 final BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices(); 322 if (bondedDevices == null) { 323 return devices; 324 } 325 synchronized (mStateMachines) { 326 for (BluetoothDevice device : bondedDevices) { 327 final ParcelUuid[] featureUuids = device.getUuids(); 328 if (!ArrayUtils.contains(featureUuids, BluetoothUuid.LE_AUDIO)) { 329 continue; 330 } 331 int connectionState = BluetoothProfile.STATE_DISCONNECTED; 332 LeAudioStateMachine sm = mStateMachines.get(device); 333 if (sm != null) { 334 connectionState = sm.getConnectionState(); 335 } 336 for (int state : states) { 337 if (connectionState == state) { 338 devices.add(device); 339 break; 340 } 341 } 342 } 343 return devices; 344 } 345 } 346 347 /** 348 * Get the list of devices that have state machines. 349 * 350 * @return the list of devices that have state machines 351 */ 352 @VisibleForTesting getDevices()353 List<BluetoothDevice> getDevices() { 354 List<BluetoothDevice> devices = new ArrayList<>(); 355 synchronized (mStateMachines) { 356 for (LeAudioStateMachine sm : mStateMachines.values()) { 357 devices.add(sm.getDevice()); 358 } 359 return devices; 360 } 361 } 362 363 /** 364 * Get the current connection state of the profile 365 * 366 * @param device is the remote bluetooth device 367 * @return {@link BluetoothProfile#STATE_DISCONNECTED} if this profile is disconnected, 368 * {@link BluetoothProfile#STATE_CONNECTING} if this profile is being connected, 369 * {@link BluetoothProfile#STATE_CONNECTED} if this profile is connected, or 370 * {@link BluetoothProfile#STATE_DISCONNECTING} if this profile is being disconnected 371 */ getConnectionState(BluetoothDevice device)372 public int getConnectionState(BluetoothDevice device) { 373 synchronized (mStateMachines) { 374 LeAudioStateMachine sm = mStateMachines.get(device); 375 if (sm == null) { 376 return BluetoothProfile.STATE_DISCONNECTED; 377 } 378 return sm.getConnectionState(); 379 } 380 } 381 382 /** 383 * Set the active device. 384 * 385 * @param device the new active device 386 * @return true on success, otherwise false 387 */ setActiveDevice(BluetoothDevice device)388 public boolean setActiveDevice(BluetoothDevice device) { 389 if (DBG) { 390 Log.d(TAG, "setActiveDevice:" + device); 391 } 392 393 return false; 394 } 395 396 /** 397 * Get the connected physical LeAudio devices that are active. 398 * 399 * @return the list of active devices. 400 */ getActiveDevices()401 List<BluetoothDevice> getActiveDevices() { 402 if (DBG) { 403 Log.d(TAG, "getActiveDevices"); 404 } 405 ArrayList<BluetoothDevice> activeDevices = new ArrayList<>(); 406 return activeDevices; 407 } 408 409 // Suppressed since this is part of a local process 410 @SuppressLint("AndroidFrameworkRequiresPermission") messageFromNative(LeAudioStackEvent stackEvent)411 void messageFromNative(LeAudioStackEvent stackEvent) { 412 Log.d(TAG, "Message from native: " + stackEvent); 413 BluetoothDevice device = stackEvent.device; 414 415 if (stackEvent.type == LeAudioStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED) { 416 // Some events require device state machine 417 synchronized (mStateMachines) { 418 LeAudioStateMachine sm = mStateMachines.get(device); 419 if (sm == null) { 420 switch (stackEvent.valueInt1) { 421 case LeAudioStackEvent.CONNECTION_STATE_CONNECTED: 422 case LeAudioStackEvent.CONNECTION_STATE_CONNECTING: 423 sm = getOrCreateStateMachine(device); 424 break; 425 default: 426 break; 427 } 428 } 429 430 if (sm == null) { 431 Log.e(TAG, "Cannot process stack event: no state machine: " + stackEvent); 432 return; 433 } 434 435 sm.sendMessage(LeAudioStateMachine.STACK_EVENT, stackEvent); 436 return; 437 } 438 } 439 440 // Some events do not require device state machine 441 if (stackEvent.type == LeAudioStackEvent.EVENT_TYPE_GROUP_STATUS_CHANGED) { 442 int group_id = stackEvent.valueInt1; 443 int group_status = stackEvent.valueInt2; 444 int group_flags = stackEvent.valueInt3; 445 446 // TODO: Handle Stream events 447 switch (group_status) { 448 case LeAudioStackEvent.GROUP_STATUS_IDLE: 449 case LeAudioStackEvent.GROUP_STATUS_RECONFIGURED: 450 case LeAudioStackEvent.GROUP_STATUS_DESTROYED: 451 case LeAudioStackEvent.GROUP_STATUS_SUSPENDED: 452 setActiveDevice(null); 453 // TODO: Get all devices with a group and Unassign? or they are already unasigned 454 // This event may come after removing all the nodes from a certain group but only that. 455 break; 456 case LeAudioStackEvent.GROUP_STATUS_STREAMING: 457 BluetoothDevice streaming_device = getConnectedPeerDevices(group_id).get(0); 458 setActiveDevice(streaming_device); 459 break; 460 default: 461 break; 462 } 463 464 } 465 } 466 getOrCreateStateMachine(BluetoothDevice device)467 private LeAudioStateMachine getOrCreateStateMachine(BluetoothDevice device) { 468 if (device == null) { 469 Log.e(TAG, "getOrCreateStateMachine failed: device cannot be null"); 470 return null; 471 } 472 synchronized (mStateMachines) { 473 LeAudioStateMachine sm = mStateMachines.get(device); 474 if (sm != null) { 475 return sm; 476 } 477 // Limit the maximum number of state machines to avoid DoS attack 478 if (mStateMachines.size() >= MAX_LE_AUDIO_STATE_MACHINES) { 479 Log.e(TAG, "Maximum number of LeAudio state machines reached: " 480 + MAX_LE_AUDIO_STATE_MACHINES); 481 return null; 482 } 483 if (DBG) { 484 Log.d(TAG, "Creating a new state machine for " + device); 485 } 486 sm = LeAudioStateMachine.make(device, this, 487 mLeAudioNativeInterface, mStateMachinesThread.getLooper()); 488 mStateMachines.put(device, sm); 489 return sm; 490 } 491 } 492 493 // Remove state machine if the bonding for a device is removed 494 private class BondStateChangedReceiver extends BroadcastReceiver { 495 @Override onReceive(Context context, Intent intent)496 public void onReceive(Context context, Intent intent) { 497 if (!BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(intent.getAction())) { 498 return; 499 } 500 int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, 501 BluetoothDevice.ERROR); 502 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 503 Objects.requireNonNull(device, "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE"); 504 bondStateChanged(device, state); 505 } 506 } 507 508 /** 509 * Process a change in the bonding state for a device. 510 * 511 * @param device the device whose bonding state has changed 512 * @param bondState the new bond state for the device. Possible values are: 513 * {@link BluetoothDevice#BOND_NONE}, 514 * {@link BluetoothDevice#BOND_BONDING}, 515 * {@link BluetoothDevice#BOND_BONDED}. 516 */ 517 @VisibleForTesting bondStateChanged(BluetoothDevice device, int bondState)518 void bondStateChanged(BluetoothDevice device, int bondState) { 519 if (DBG) { 520 Log.d(TAG, "Bond state changed for device: " + device + " state: " + bondState); 521 } 522 // Remove state machine if the bonding for a device is removed 523 if (bondState != BluetoothDevice.BOND_NONE) { 524 return; 525 } 526 mDeviceGroupIdMap.remove(device); 527 synchronized (mStateMachines) { 528 LeAudioStateMachine sm = mStateMachines.get(device); 529 if (sm == null) { 530 return; 531 } 532 if (sm.getConnectionState() != BluetoothProfile.STATE_DISCONNECTED) { 533 return; 534 } 535 removeStateMachine(device); 536 } 537 } 538 removeStateMachine(BluetoothDevice device)539 private void removeStateMachine(BluetoothDevice device) { 540 synchronized (mStateMachines) { 541 LeAudioStateMachine sm = mStateMachines.get(device); 542 if (sm == null) { 543 Log.w(TAG, "removeStateMachine: device " + device 544 + " does not have a state machine"); 545 return; 546 } 547 Log.i(TAG, "removeStateMachine: removing state machine for device: " + device); 548 sm.doQuit(); 549 sm.cleanup(); 550 mStateMachines.remove(device); 551 } 552 } 553 getConnectedPeerDevices(int groupId)554 private List<BluetoothDevice> getConnectedPeerDevices(int groupId) { 555 List<BluetoothDevice> result = new ArrayList<>(); 556 for (BluetoothDevice peerDevice : getConnectedDevices()) { 557 if (getGroupId(peerDevice) == groupId) { 558 result.add(peerDevice); 559 } 560 } 561 return result; 562 } 563 564 @VisibleForTesting connectionStateChanged(BluetoothDevice device, int fromState, int toState)565 synchronized void connectionStateChanged(BluetoothDevice device, int fromState, 566 int toState) { 567 if ((device == null) || (fromState == toState)) { 568 Log.e(TAG, "connectionStateChanged: unexpected invocation. device=" + device 569 + " fromState=" + fromState + " toState=" + toState); 570 return; 571 } 572 if (toState == BluetoothProfile.STATE_CONNECTED) { 573 int myGroupId = getGroupId(device); 574 if (myGroupId == LE_AUDIO_GROUP_ID_INVALID 575 || getConnectedPeerDevices(myGroupId).size() == 1) { 576 // Log LE Audio connection event if we are the first device in a set 577 // Or when the GroupId has not been found 578 // MetricsLogger.logProfileConnectionEvent( 579 // BluetoothMetricsProto.ProfileId.LE_AUDIO); 580 } 581 if (!mGroupIdConnectedMap.getOrDefault(myGroupId, false)) { 582 mGroupIdConnectedMap.put(myGroupId, true); 583 } 584 } 585 if (fromState == BluetoothProfile.STATE_CONNECTED && getConnectedDevices().isEmpty()) { 586 setActiveDevice(null); 587 int myGroupId = getGroupId(device); 588 mGroupIdConnectedMap.put(myGroupId, false); 589 } 590 // Check if the device is disconnected - if unbond, remove the state machine 591 if (toState == BluetoothProfile.STATE_DISCONNECTED) { 592 int bondState = mAdapterService.getBondState(device); 593 if (bondState == BluetoothDevice.BOND_NONE) { 594 if (DBG) { 595 Log.d(TAG, device + " is unbond. Remove state machine"); 596 } 597 removeStateMachine(device); 598 } 599 } 600 } 601 602 private class ConnectionStateChangedReceiver extends BroadcastReceiver { 603 @Override onReceive(Context context, Intent intent)604 public void onReceive(Context context, Intent intent) { 605 if (!BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED.equals(intent.getAction())) { 606 return; 607 } 608 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 609 int toState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); 610 int fromState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1); 611 connectionStateChanged(device, fromState, toState); 612 } 613 } 614 615 /** 616 * Check whether can connect to a peer device. 617 * The check considers a number of factors during the evaluation. 618 * 619 * @param device the peer device to connect to 620 * @return true if connection is allowed, otherwise false 621 */ okToConnect(BluetoothDevice device)622 public boolean okToConnect(BluetoothDevice device) { 623 // Check if this is an incoming connection in Quiet mode. 624 if (mAdapterService.isQuietModeEnabled()) { 625 Log.e(TAG, "okToConnect: cannot connect to " + device + " : quiet mode enabled"); 626 return false; 627 } 628 // Check connectionPolicy and accept or reject the connection. 629 int connectionPolicy = getConnectionPolicy(device); 630 int bondState = mAdapterService.getBondState(device); 631 // Allow this connection only if the device is bonded. Any attempt to connect while 632 // bonding would potentially lead to an unauthorized connection. 633 if (bondState != BluetoothDevice.BOND_BONDED) { 634 Log.w(TAG, "okToConnect: return false, bondState=" + bondState); 635 return false; 636 } else if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_UNKNOWN 637 && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) { 638 // Otherwise, reject the connection if connectionPolicy is not valid. 639 Log.w(TAG, "okToConnect: return false, connectionPolicy=" + connectionPolicy); 640 return false; 641 } 642 return true; 643 } 644 645 /** 646 * Set connection policy of the profile and connects it if connectionPolicy is 647 * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED} or disconnects if connectionPolicy is 648 * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN} 649 * 650 * <p> The device should already be paired. 651 * Connection policy can be one of: 652 * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED}, 653 * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}, 654 * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN} 655 * 656 * @param device the remote device 657 * @param connectionPolicy is the connection policy to set to for this profile 658 * @return true on success, otherwise false 659 */ 660 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) setConnectionPolicy(BluetoothDevice device, int connectionPolicy)661 public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) { 662 enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, 663 "Need BLUETOOTH_PRIVILEGED permission"); 664 if (DBG) { 665 Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy); 666 } 667 668 if (!mDatabaseManager.setProfileConnectionPolicy(device, BluetoothProfile.LE_AUDIO, 669 connectionPolicy)) { 670 return false; 671 } 672 if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) { 673 connect(device); 674 } else if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) { 675 disconnect(device); 676 } 677 return true; 678 } 679 680 /** 681 * Get the connection policy of the profile. 682 * 683 * <p> The connection policy can be any of: 684 * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED}, 685 * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}, 686 * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN} 687 * 688 * @param device Bluetooth device 689 * @return connection policy of the device 690 * @hide 691 */ getConnectionPolicy(BluetoothDevice device)692 public int getConnectionPolicy(BluetoothDevice device) { 693 return mDatabaseManager 694 .getProfileConnectionPolicy(device, BluetoothProfile.LE_AUDIO); 695 } 696 697 /** 698 * Get device group id. Devices with same group id belong to same group (i.e left and right 699 * earbud) 700 * @param device LE Audio capable device 701 * @return group id that this device currently belongs to 702 */ getGroupId(BluetoothDevice device)703 public int getGroupId(BluetoothDevice device) { 704 if (device == null) { 705 return LE_AUDIO_GROUP_ID_INVALID; 706 } 707 //TODO: implement 708 return LE_AUDIO_GROUP_ID_INVALID; 709 } 710 711 /** 712 * Binder object: must be a static class or memory leak may occur 713 */ 714 @VisibleForTesting 715 static class BluetoothLeAudioBinder extends IBluetoothLeAudio.Stub 716 implements IProfileServiceBinder { 717 private LeAudioService mService; 718 719 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getService(AttributionSource source)720 private LeAudioService getService(AttributionSource source) { 721 if (!Utils.checkCallerIsSystemOrActiveUser(TAG) 722 || !Utils.checkServiceAvailable(mService, TAG) 723 || !Utils.checkConnectPermissionForDataDelivery(mService, source, TAG)) { 724 return null; 725 } 726 return mService; 727 } 728 BluetoothLeAudioBinder(LeAudioService svc)729 BluetoothLeAudioBinder(LeAudioService svc) { 730 mService = svc; 731 } 732 733 @Override cleanup()734 public void cleanup() { 735 mService = null; 736 } 737 738 @Override connect(BluetoothDevice device, AttributionSource source)739 public boolean connect(BluetoothDevice device, AttributionSource source) { 740 Attributable.setAttributionSource(device, source); 741 LeAudioService service = getService(source); 742 if (service == null) { 743 return false; 744 } 745 return service.connect(device); 746 } 747 748 @Override disconnect(BluetoothDevice device, AttributionSource source)749 public boolean disconnect(BluetoothDevice device, AttributionSource source) { 750 Attributable.setAttributionSource(device, source); 751 LeAudioService service = getService(source); 752 if (service == null) { 753 return false; 754 } 755 return service.disconnect(device); 756 } 757 758 @Override getConnectedDevices(AttributionSource source)759 public List<BluetoothDevice> getConnectedDevices(AttributionSource source) { 760 LeAudioService service = getService(source); 761 if (service == null) { 762 return new ArrayList<>(0); 763 } 764 return service.getConnectedDevices(); 765 } 766 767 @Override getDevicesMatchingConnectionStates(int[] states, AttributionSource source)768 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states, 769 AttributionSource source) { 770 LeAudioService service = getService(source); 771 if (service == null) { 772 return new ArrayList<>(0); 773 } 774 return service.getDevicesMatchingConnectionStates(states); 775 } 776 777 @Override getConnectionState(BluetoothDevice device, AttributionSource source)778 public int getConnectionState(BluetoothDevice device, AttributionSource source) { 779 Attributable.setAttributionSource(device, source); 780 LeAudioService service = getService(source); 781 if (service == null) { 782 return BluetoothProfile.STATE_DISCONNECTED; 783 } 784 return service.getConnectionState(device); 785 } 786 787 @Override setActiveDevice(BluetoothDevice device, AttributionSource source)788 public boolean setActiveDevice(BluetoothDevice device, AttributionSource source) { 789 Attributable.setAttributionSource(device, source); 790 LeAudioService service = getService(source); 791 if (service == null) { 792 return false; 793 } 794 return service.setActiveDevice(device); 795 } 796 797 @Override getActiveDevices(AttributionSource source)798 public List<BluetoothDevice> getActiveDevices(AttributionSource source) { 799 LeAudioService service = getService(source); 800 if (service == null) { 801 return new ArrayList<>(); 802 } 803 return service.getActiveDevices(); 804 } 805 806 @Override setConnectionPolicy(BluetoothDevice device, int connectionPolicy, AttributionSource source)807 public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy, 808 AttributionSource source) { 809 Attributable.setAttributionSource(device, source); 810 LeAudioService service = getService(source); 811 if (service == null) { 812 return false; 813 } 814 return service.setConnectionPolicy(device, connectionPolicy); 815 } 816 817 @Override getConnectionPolicy(BluetoothDevice device, AttributionSource source)818 public int getConnectionPolicy(BluetoothDevice device, AttributionSource source) { 819 Attributable.setAttributionSource(device, source); 820 LeAudioService service = getService(source); 821 if (service == null) { 822 return BluetoothProfile.CONNECTION_POLICY_UNKNOWN; 823 } 824 return service.getConnectionPolicy(device); 825 } 826 827 @Override getGroupId(BluetoothDevice device, AttributionSource source)828 public int getGroupId(BluetoothDevice device, AttributionSource source) { 829 Attributable.setAttributionSource(device, source); 830 LeAudioService service = getService(source); 831 if (service == null) { 832 return LE_AUDIO_GROUP_ID_INVALID; 833 } 834 835 return service.getGroupId(device); 836 } 837 } 838 839 @Override dump(StringBuilder sb)840 public void dump(StringBuilder sb) { 841 super.dump(sb); 842 // TODO: Dump all state machines 843 } 844 } 845