1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settingslib.bluetooth; 18 19 import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; 20 import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; 21 22 import android.annotation.CallbackExecutor; 23 import android.bluetooth.BluetoothAdapter; 24 import android.bluetooth.BluetoothClass; 25 import android.bluetooth.BluetoothDevice; 26 import android.bluetooth.BluetoothLeBroadcastAssistant; 27 import android.bluetooth.BluetoothLeBroadcastMetadata; 28 import android.bluetooth.BluetoothLeBroadcastReceiveState; 29 import android.bluetooth.BluetoothProfile; 30 import android.bluetooth.BluetoothProfile.ServiceListener; 31 import android.content.Context; 32 import android.os.Build; 33 import android.util.Log; 34 35 import androidx.annotation.IntRange; 36 import androidx.annotation.NonNull; 37 import androidx.annotation.Nullable; 38 import androidx.annotation.RequiresApi; 39 40 import com.android.settingslib.R; 41 42 import java.util.ArrayList; 43 import java.util.List; 44 import java.util.Map; 45 import java.util.concurrent.ConcurrentHashMap; 46 import java.util.concurrent.Executor; 47 import java.util.concurrent.Executors; 48 49 /** 50 * LocalBluetoothLeBroadcastAssistant provides an interface between the Settings app and the 51 * functionality of the local {@link BluetoothLeBroadcastAssistant}. Use the {@link 52 * BluetoothLeBroadcastAssistant.Callback} to get the result callback. 53 */ 54 public class LocalBluetoothLeBroadcastAssistant implements LocalBluetoothProfile { 55 /** A derived source state based on {@link BluetoothLeBroadcastReceiveState}. */ 56 public enum LocalBluetoothLeBroadcastSourceState { 57 UNKNOWN, 58 STREAMING, 59 DECRYPTION_FAILED, 60 PAUSED, 61 } 62 63 private static final String TAG = "LocalBluetoothLeBroadcastAssistant"; 64 private static final int UNKNOWN_VALUE_PLACEHOLDER = -1; 65 private static final boolean DEBUG = BluetoothUtils.D; 66 67 static final String NAME = "LE_AUDIO_BROADCAST_ASSISTANT"; 68 // Order of this profile in device profiles list 69 private static final int ORDINAL = 1; 70 // Referring to Broadcast Audio Scan Service 1.0 71 // Table 3.9: Broadcast Receive State characteristic format 72 // 0x00000000: 0b0 = Not synchronized to BIS_index[x] 73 // 0xFFFFFFFF: Failed to sync to BIG 74 private static final long BIS_SYNC_NOT_SYNC_TO_BIS = 0x00000000L; 75 private static final long BIS_SYNC_FAILED_SYNC_TO_BIG = 0xFFFFFFFFL; 76 private static final String EMPTY_DEVICE_ADDRESS = "00:00:00:00:00:00"; 77 78 private LocalBluetoothProfileManager mProfileManager; 79 private BluetoothLeBroadcastAssistant mService; 80 private final CachedBluetoothDeviceManager mDeviceManager; 81 private BluetoothLeBroadcastMetadata mBluetoothLeBroadcastMetadata; 82 private BluetoothLeBroadcastMetadata.Builder mBuilder; 83 private boolean mIsProfileReady; 84 private Executor mExecutor; 85 // Cached assistant callbacks being register before service is connected. 86 private final Map<BluetoothLeBroadcastAssistant.Callback, Executor> mCachedCallbackExecutorMap = 87 new ConcurrentHashMap<>(); 88 89 private final ServiceListener mServiceListener = 90 new ServiceListener() { 91 @Override 92 public void onServiceConnected(int profile, BluetoothProfile proxy) { 93 if (DEBUG) { 94 Log.d(TAG, "Bluetooth service connected"); 95 } 96 mService = (BluetoothLeBroadcastAssistant) proxy; 97 // We just bound to the service, so refresh the UI for any connected LeAudio 98 // devices. 99 List<BluetoothDevice> deviceList = mService.getConnectedDevices(); 100 while (!deviceList.isEmpty()) { 101 BluetoothDevice nextDevice = deviceList.remove(0); 102 CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice); 103 // we may add a new device here, but generally this should not happen 104 if (device == null) { 105 if (DEBUG) { 106 Log.d( 107 TAG, 108 "LocalBluetoothLeBroadcastAssistant found new device: " 109 + nextDevice); 110 } 111 device = mDeviceManager.addDevice(nextDevice); 112 } 113 device.onProfileStateChanged( 114 LocalBluetoothLeBroadcastAssistant.this, 115 BluetoothProfile.STATE_CONNECTED); 116 device.refresh(); 117 } 118 119 mProfileManager.callServiceConnectedListeners(); 120 if (!mIsProfileReady) { 121 mIsProfileReady = true; 122 registerServiceCallBack(mExecutor, mAssistantCallback); 123 if (DEBUG) { 124 Log.d( 125 TAG, 126 "onServiceConnected, register mCachedCallbackExecutorMap = " 127 + mCachedCallbackExecutorMap); 128 } 129 mCachedCallbackExecutorMap.forEach( 130 (callback, executor) -> registerServiceCallBack(executor, 131 callback)); 132 } 133 } 134 135 @Override 136 public void onServiceDisconnected(int profile) { 137 if (profile != BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT) { 138 Log.d(TAG, "The profile is not LE_AUDIO_BROADCAST_ASSISTANT"); 139 return; 140 } 141 if (DEBUG) { 142 Log.d(TAG, "Bluetooth service disconnected"); 143 } 144 mProfileManager.callServiceDisconnectedListeners(); 145 if (mIsProfileReady) { 146 mIsProfileReady = false; 147 unregisterServiceCallBack(mAssistantCallback); 148 mCachedCallbackExecutorMap.clear(); 149 } 150 } 151 }; 152 153 private final BluetoothLeBroadcastAssistant.Callback mAssistantCallback = 154 new BluetoothLeBroadcastAssistant.Callback() { 155 @Override 156 public void onSourceAdded(@NonNull BluetoothDevice sink, int sourceId, int reason) { 157 } 158 159 @Override 160 public void onSearchStarted(int reason) {} 161 162 @Override 163 public void onSearchStartFailed(int reason) {} 164 165 @Override 166 public void onSearchStopped(int reason) {} 167 168 @Override 169 public void onSearchStopFailed(int reason) {} 170 171 @Override 172 public void onSourceFound(@NonNull BluetoothLeBroadcastMetadata source) {} 173 174 @Override 175 public void onSourceAddFailed( 176 @NonNull BluetoothDevice sink, 177 @NonNull BluetoothLeBroadcastMetadata source, 178 int reason) {} 179 180 @Override 181 public void onSourceModified( 182 @NonNull BluetoothDevice sink, int sourceId, int reason) {} 183 184 @Override 185 public void onSourceModifyFailed( 186 @NonNull BluetoothDevice sink, int sourceId, int reason) {} 187 188 @Override 189 public void onSourceRemoved( 190 @NonNull BluetoothDevice sink, int sourceId, int reason) {} 191 192 @Override 193 public void onSourceRemoveFailed( 194 @NonNull BluetoothDevice sink, int sourceId, int reason) {} 195 196 @Override 197 public void onReceiveStateChanged( 198 @NonNull BluetoothDevice sink, 199 int sourceId, 200 @NonNull BluetoothLeBroadcastReceiveState state) {} 201 }; 202 LocalBluetoothLeBroadcastAssistant( Context context, CachedBluetoothDeviceManager deviceManager, LocalBluetoothProfileManager profileManager)203 public LocalBluetoothLeBroadcastAssistant( 204 Context context, 205 CachedBluetoothDeviceManager deviceManager, 206 LocalBluetoothProfileManager profileManager) { 207 mProfileManager = profileManager; 208 mDeviceManager = deviceManager; 209 mExecutor = Executors.newSingleThreadExecutor(); 210 BluetoothAdapter.getDefaultAdapter() 211 .getProfileProxy( 212 context, mServiceListener, BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT); 213 mBuilder = new BluetoothLeBroadcastMetadata.Builder(); 214 } 215 216 /** 217 * Add a Broadcast Source to the Broadcast Sink with {@link BluetoothLeBroadcastMetadata}. 218 * 219 * @param sink Broadcast Sink to which the Broadcast Source should be added 220 * @param metadata Broadcast Source metadata to be added to the Broadcast Sink 221 * @param isGroupOp {@code true} if Application wants to perform this operation for all 222 * coordinated set members throughout this session. Otherwise, caller would have to add, 223 * modify, and remove individual set members. 224 */ addSource( BluetoothDevice sink, BluetoothLeBroadcastMetadata metadata, boolean isGroupOp)225 public void addSource( 226 BluetoothDevice sink, BluetoothLeBroadcastMetadata metadata, boolean isGroupOp) { 227 if (mService == null) { 228 Log.d(TAG, "The BluetoothLeBroadcastAssistant is null"); 229 return; 230 } 231 try { 232 mService.addSource(sink, metadata, isGroupOp); 233 } catch (IllegalStateException e) { 234 // BT will check callback registration before add source. 235 // If it throw callback exception when bt is disabled, then the failure is intended, 236 // just catch it here. 237 BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); 238 if (adapter != null && adapter.isEnabled()) { 239 throw e; 240 } else { 241 Log.d(TAG, "Catch addSource failure when bt is disabled: " + e); 242 } 243 } 244 } 245 246 /** 247 * Add a Broadcast Source to the Broadcast Sink with the information which are separated from 248 * the qr code string. 249 * 250 * @param sink Broadcast Sink to which the Broadcast Source should be added 251 * @param sourceAddressType hardware MAC Address of the device. See {@link 252 * BluetoothDevice.AddressType}. 253 * @param presentationDelayMicros presentation delay of this Broadcast Source in microseconds. 254 * @param sourceAdvertisingSid 1-byte long Advertising_SID of the Broadcast Source. 255 * @param broadcastId 3-byte long Broadcast_ID of the Broadcast Source. 256 * @param paSyncInterval Periodic Advertising Sync interval of the broadcast Source, {@link 257 * BluetoothLeBroadcastMetadata#PA_SYNC_INTERVAL_UNKNOWN} if unknown. 258 * @param isEncrypted whether the Broadcast Source is encrypted. 259 * @param broadcastCode Broadcast Code for this Broadcast Source, null if code is not required. 260 * @param sourceDevice source advertiser address. 261 * @param isGroupOp {@code true} if Application wants to perform this operation for all 262 * coordinated set members throughout this session. Otherwise, caller would have to add, 263 * modify, and remove individual set members. 264 */ addSource( @onNull BluetoothDevice sink, int sourceAddressType, int presentationDelayMicros, int sourceAdvertisingSid, int broadcastId, int paSyncInterval, boolean isEncrypted, byte[] broadcastCode, BluetoothDevice sourceDevice, boolean isGroupOp)265 public void addSource( 266 @NonNull BluetoothDevice sink, 267 int sourceAddressType, 268 int presentationDelayMicros, 269 int sourceAdvertisingSid, 270 int broadcastId, 271 int paSyncInterval, 272 boolean isEncrypted, 273 byte[] broadcastCode, 274 BluetoothDevice sourceDevice, 275 boolean isGroupOp) { 276 if (DEBUG) { 277 Log.d(TAG, "addSource()"); 278 } 279 buildMetadata( 280 sourceAddressType, 281 presentationDelayMicros, 282 sourceAdvertisingSid, 283 broadcastId, 284 paSyncInterval, 285 isEncrypted, 286 broadcastCode, 287 sourceDevice); 288 addSource(sink, mBluetoothLeBroadcastMetadata, isGroupOp); 289 } 290 buildMetadata( int sourceAddressType, int presentationDelayMicros, int sourceAdvertisingSid, int broadcastId, int paSyncInterval, boolean isEncrypted, byte[] broadcastCode, BluetoothDevice sourceDevice)291 private void buildMetadata( 292 int sourceAddressType, 293 int presentationDelayMicros, 294 int sourceAdvertisingSid, 295 int broadcastId, 296 int paSyncInterval, 297 boolean isEncrypted, 298 byte[] broadcastCode, 299 BluetoothDevice sourceDevice) { 300 mBluetoothLeBroadcastMetadata = 301 mBuilder.setSourceDevice(sourceDevice, sourceAddressType) 302 .setSourceAdvertisingSid(sourceAdvertisingSid) 303 .setBroadcastId(broadcastId) 304 .setPaSyncInterval(paSyncInterval) 305 .setEncrypted(isEncrypted) 306 .setBroadcastCode(broadcastCode) 307 .setPresentationDelayMicros(presentationDelayMicros) 308 .build(); 309 } 310 removeSource(@onNull BluetoothDevice sink, int sourceId)311 public void removeSource(@NonNull BluetoothDevice sink, int sourceId) { 312 if (DEBUG) { 313 Log.d(TAG, "removeSource()"); 314 } 315 if (mService == null) { 316 Log.d(TAG, "The BluetoothLeBroadcastAssistant is null"); 317 return; 318 } 319 mService.removeSource(sink, sourceId); 320 } 321 startSearchingForSources(@onNull List<android.bluetooth.le.ScanFilter> filters)322 public void startSearchingForSources(@NonNull List<android.bluetooth.le.ScanFilter> filters) { 323 if (DEBUG) { 324 Log.d(TAG, "startSearchingForSources()"); 325 } 326 if (mService == null) { 327 Log.d(TAG, "The BluetoothLeBroadcastAssistant is null"); 328 return; 329 } 330 mService.startSearchingForSources(filters); 331 } 332 333 /** 334 * Return true if a search has been started by this application. 335 * 336 * @return true if a search has been started by this application 337 * @hide 338 */ isSearchInProgress()339 public boolean isSearchInProgress() { 340 if (DEBUG) { 341 Log.d(TAG, "isSearchInProgress()"); 342 } 343 if (mService == null) { 344 Log.d(TAG, "The BluetoothLeBroadcastAssistant is null"); 345 return false; 346 } 347 return mService.isSearchInProgress(); 348 } 349 350 /** 351 * Stops an ongoing search for nearby Broadcast Sources. 352 * 353 * <p>On success, {@link BluetoothLeBroadcastAssistant.Callback#onSearchStopped(int)} will be 354 * called with reason code {@link BluetoothStatusCodes#REASON_LOCAL_APP_REQUEST}. On failure, 355 * {@link BluetoothLeBroadcastAssistant.Callback#onSearchStopFailed(int)} will be called with 356 * reason code 357 * 358 * @throws IllegalStateException if callback was not registered 359 */ stopSearchingForSources()360 public void stopSearchingForSources() { 361 if (DEBUG) { 362 Log.d(TAG, "stopSearchingForSources()"); 363 } 364 if (mService == null) { 365 Log.d(TAG, "The BluetoothLeBroadcastAssistant is null"); 366 return; 367 } 368 mService.stopSearchingForSources(); 369 } 370 371 /** 372 * Get information about all Broadcast Sources that a Broadcast Sink knows about. 373 * 374 * @param sink Broadcast Sink from which to get all Broadcast Sources 375 * @return the list of Broadcast Receive State {@link BluetoothLeBroadcastReceiveState} stored 376 * in the Broadcast Sink 377 * @throws NullPointerException when <var>sink</var> is null 378 */ getAllSources( @onNull BluetoothDevice sink)379 public @NonNull List<BluetoothLeBroadcastReceiveState> getAllSources( 380 @NonNull BluetoothDevice sink) { 381 if (DEBUG) { 382 Log.d(TAG, "getAllSources()"); 383 } 384 if (mService == null) { 385 Log.d(TAG, "The BluetoothLeBroadcastAssistant is null"); 386 return new ArrayList<BluetoothLeBroadcastReceiveState>(); 387 } 388 return mService.getAllSources(sink); 389 } 390 391 /** 392 * Gets the {@link BluetoothLeBroadcastMetadata} of a specified source added to this sink. 393 * 394 * @param sink Broadcast Sink device 395 * @param sourceId Broadcast source id 396 * @return metadata {@link BluetoothLeBroadcastMetadata} associated with the specified source. 397 */ getSourceMetadata( @onNull BluetoothDevice sink, @IntRange(from = 0x00, to = 0xFF) int sourceId)398 public @Nullable BluetoothLeBroadcastMetadata getSourceMetadata( 399 @NonNull BluetoothDevice sink, @IntRange(from = 0x00, to = 0xFF) int sourceId) { 400 if (mService == null) { 401 Log.d(TAG, "The BluetoothLeBroadcastAssistant is null"); 402 return null; 403 } 404 try { 405 return mService.getSourceMetadata(sink, sourceId); 406 } catch (IllegalArgumentException | NoSuchMethodError e) { 407 Log.w(TAG, "Error calling getSourceMetadata()", e); 408 } 409 return null; 410 } 411 412 /** 413 * Register Broadcast Assistant Callbacks to track its state and receivers 414 * 415 * @param executor Executor object for callback 416 * @param callback Callback object to be registered 417 */ registerServiceCallBack( @onNull @allbackExecutor Executor executor, @NonNull BluetoothLeBroadcastAssistant.Callback callback)418 public void registerServiceCallBack( 419 @NonNull @CallbackExecutor Executor executor, 420 @NonNull BluetoothLeBroadcastAssistant.Callback callback) { 421 if (mService == null) { 422 Log.d( 423 TAG, 424 "registerServiceCallBack failed, the BluetoothLeBroadcastAssistant is null."); 425 mCachedCallbackExecutorMap.putIfAbsent(callback, executor); 426 return; 427 } 428 429 try { 430 mService.registerCallback(executor, callback); 431 } catch (IllegalArgumentException e) { 432 Log.w(TAG, "registerServiceCallBack failed. " + e.getMessage()); 433 } 434 } 435 436 /** 437 * Unregister previously registered Broadcast Assistant Callbacks 438 * 439 * @param callback Callback object to be unregistered 440 */ unregisterServiceCallBack( @onNull BluetoothLeBroadcastAssistant.Callback callback)441 public void unregisterServiceCallBack( 442 @NonNull BluetoothLeBroadcastAssistant.Callback callback) { 443 mCachedCallbackExecutorMap.remove(callback); 444 if (mService == null) { 445 Log.d( 446 TAG, 447 "unregisterServiceCallBack failed, the BluetoothLeBroadcastAssistant is null."); 448 return; 449 } 450 451 try { 452 mService.unregisterCallback(callback); 453 } catch (IllegalArgumentException e) { 454 Log.w(TAG, "unregisterServiceCallBack failed. " + e.getMessage()); 455 } 456 } 457 isProfileReady()458 public boolean isProfileReady() { 459 return mIsProfileReady; 460 } 461 getProfileId()462 public int getProfileId() { 463 return BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT; 464 } 465 accessProfileEnabled()466 public boolean accessProfileEnabled() { 467 return false; 468 } 469 isAutoConnectable()470 public boolean isAutoConnectable() { 471 return true; 472 } 473 getConnectionStatus(BluetoothDevice device)474 public int getConnectionStatus(BluetoothDevice device) { 475 if (mService == null) { 476 return BluetoothProfile.STATE_DISCONNECTED; 477 } 478 // LE Audio Broadcasts are not connection-oriented. 479 return mService.getConnectionState(device); 480 } 481 getConnectedDevices()482 public List<BluetoothDevice> getConnectedDevices() { 483 if (mService == null) { 484 return new ArrayList<BluetoothDevice>(0); 485 } 486 return mService.getDevicesMatchingConnectionStates( 487 new int[] { 488 BluetoothProfile.STATE_CONNECTED, 489 BluetoothProfile.STATE_CONNECTING, 490 BluetoothProfile.STATE_DISCONNECTING 491 }); 492 } 493 494 /** Gets devices with matched connection states. */ getDevicesMatchingConnectionStates(@onNull int[] states)495 public List<BluetoothDevice> getDevicesMatchingConnectionStates(@NonNull int[] states) { 496 if (mService == null) { 497 return new ArrayList<BluetoothDevice>(0); 498 } 499 return mService.getDevicesMatchingConnectionStates(states); 500 } 501 502 /** Gets all connected devices on assistant profile. */ getAllConnectedDevices()503 public List<BluetoothDevice> getAllConnectedDevices() { 504 if (mService == null) { 505 return new ArrayList<BluetoothDevice>(0); 506 } 507 return mService.getConnectedDevices(); 508 } 509 isEnabled(BluetoothDevice device)510 public boolean isEnabled(BluetoothDevice device) { 511 if (mService == null || device == null) { 512 return false; 513 } 514 return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; 515 } 516 getConnectionPolicy(BluetoothDevice device)517 public int getConnectionPolicy(BluetoothDevice device) { 518 if (mService == null || device == null) { 519 return CONNECTION_POLICY_FORBIDDEN; 520 } 521 return mService.getConnectionPolicy(device); 522 } 523 setEnabled(BluetoothDevice device, boolean enabled)524 public boolean setEnabled(BluetoothDevice device, boolean enabled) { 525 boolean isEnabled = false; 526 if (mService == null || device == null) { 527 return false; 528 } 529 if (enabled) { 530 if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { 531 isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); 532 } 533 } else { 534 isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); 535 } 536 537 return isEnabled; 538 } 539 toString()540 public String toString() { 541 return NAME; 542 } 543 getOrdinal()544 public int getOrdinal() { 545 return ORDINAL; 546 } 547 getNameResource(BluetoothDevice device)548 public int getNameResource(BluetoothDevice device) { 549 return R.string.summary_empty; 550 } 551 getSummaryResourceForDevice(BluetoothDevice device)552 public int getSummaryResourceForDevice(BluetoothDevice device) { 553 int state = getConnectionStatus(device); 554 return BluetoothUtils.getConnectionStateSummary(state); 555 } 556 getDrawableResource(BluetoothClass btClass)557 public int getDrawableResource(BluetoothClass btClass) { 558 return 0; 559 } 560 561 @RequiresApi(Build.VERSION_CODES.S) finalize()562 protected void finalize() { 563 if (DEBUG) { 564 Log.d(TAG, "finalize()"); 565 } 566 if (mService != null) { 567 try { 568 BluetoothAdapter.getDefaultAdapter() 569 .closeProfileProxy(BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT, mService); 570 mService = null; 571 } catch (Throwable t) { 572 Log.w(TAG, "Error cleaning up LeAudio proxy", t); 573 } 574 } 575 } 576 577 /** Checks the source connection status based on the provided broadcast receive state. */ getLocalSourceState( BluetoothLeBroadcastReceiveState state)578 public static LocalBluetoothLeBroadcastSourceState getLocalSourceState( 579 BluetoothLeBroadcastReceiveState state) { 580 // Source is actively streaming 581 if (state.getBisSyncState().stream() 582 .anyMatch( 583 bitmap -> 584 (bitmap != BIS_SYNC_NOT_SYNC_TO_BIS 585 && bitmap != BIS_SYNC_FAILED_SYNC_TO_BIG))) { 586 return LocalBluetoothLeBroadcastSourceState.STREAMING; 587 } 588 // Wrong password is used for the source 589 if (state.getPaSyncState() == BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED 590 && state.getBigEncryptionState() 591 == BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_BAD_CODE) { 592 return LocalBluetoothLeBroadcastSourceState.DECRYPTION_FAILED; 593 } 594 // Source in hysteresis mode 595 if (!state.getSourceDevice().getAddress().equals(EMPTY_DEVICE_ADDRESS)) { 596 // Referring to Broadcast Audio Scan Service 1.0 597 // All zero address means no source on the sink device 598 return LocalBluetoothLeBroadcastSourceState.PAUSED; 599 } 600 return LocalBluetoothLeBroadcastSourceState.UNKNOWN; 601 } 602 } 603