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.bluetooth.bass_client; 18 19 import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; 20 import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; 21 import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_UNKNOWN; 22 import static android.bluetooth.BluetoothProfile.STATE_CONNECTED; 23 import static android.bluetooth.BluetoothProfile.STATE_DISCONNECTED; 24 import static android.bluetooth.IBluetoothLeAudio.LE_AUDIO_GROUP_ID_INVALID; 25 26 import static com.android.bluetooth.flags.Flags.leaudioBassScanWithInternalScanController; 27 import static com.android.bluetooth.flags.Flags.leaudioBigDependsOnAudioState; 28 import static com.android.bluetooth.flags.Flags.leaudioBroadcastApiGetLocalMetadata; 29 import static com.android.bluetooth.flags.Flags.leaudioBroadcastPreventResumeInterruption; 30 import static com.android.bluetooth.flags.Flags.leaudioBroadcastResyncHelper; 31 import static com.android.bluetooth.flags.Flags.leaudioMonitorUnicastSourceWhenManagedByBroadcastDelegator; 32 import static com.android.bluetooth.flags.Flags.leaudioSortScansToSyncByFails; 33 34 import static java.util.Objects.requireNonNull; 35 36 import android.annotation.SuppressLint; 37 import android.bluetooth.BluetoothAdapter; 38 import android.bluetooth.BluetoothDevice; 39 import android.bluetooth.BluetoothLeAudio; 40 import android.bluetooth.BluetoothLeAudioCodecConfigMetadata; 41 import android.bluetooth.BluetoothLeAudioContentMetadata; 42 import android.bluetooth.BluetoothLeBroadcastChannel; 43 import android.bluetooth.BluetoothLeBroadcastMetadata; 44 import android.bluetooth.BluetoothLeBroadcastReceiveState; 45 import android.bluetooth.BluetoothLeBroadcastSubgroup; 46 import android.bluetooth.BluetoothProfile; 47 import android.bluetooth.BluetoothStatusCodes; 48 import android.bluetooth.BluetoothUuid; 49 import android.bluetooth.IBluetoothLeBroadcastAssistantCallback; 50 import android.bluetooth.le.IScannerCallback; 51 import android.bluetooth.le.PeriodicAdvertisingCallback; 52 import android.bluetooth.le.PeriodicAdvertisingReport; 53 import android.bluetooth.le.ScanCallback; 54 import android.bluetooth.le.ScanFilter; 55 import android.bluetooth.le.ScanRecord; 56 import android.bluetooth.le.ScanResult; 57 import android.bluetooth.le.ScanSettings; 58 import android.os.Handler; 59 import android.os.HandlerThread; 60 import android.os.Looper; 61 import android.os.Message; 62 import android.os.ParcelUuid; 63 import android.os.RemoteCallbackList; 64 import android.os.RemoteException; 65 import android.provider.DeviceConfig; 66 import android.sysprop.BluetoothProperties; 67 import android.util.Log; 68 import android.util.Pair; 69 70 import com.android.bluetooth.BluetoothEventLogger; 71 import com.android.bluetooth.BluetoothMethodProxy; 72 import com.android.bluetooth.Utils; 73 import com.android.bluetooth.btservice.AdapterService; 74 import com.android.bluetooth.btservice.ProfileService; 75 import com.android.bluetooth.btservice.ServiceFactory; 76 import com.android.bluetooth.btservice.storage.DatabaseManager; 77 import com.android.bluetooth.csip.CsipSetCoordinatorService; 78 import com.android.bluetooth.le_audio.LeAudioService; 79 import com.android.bluetooth.le_scan.ScanController; 80 import com.android.internal.annotations.GuardedBy; 81 import com.android.internal.annotations.VisibleForTesting; 82 83 import java.time.Duration; 84 import java.util.ArrayList; 85 import java.util.Arrays; 86 import java.util.Collections; 87 import java.util.Comparator; 88 import java.util.HashMap; 89 import java.util.HashSet; 90 import java.util.Iterator; 91 import java.util.LinkedHashSet; 92 import java.util.List; 93 import java.util.Map; 94 import java.util.Optional; 95 import java.util.PriorityQueue; 96 import java.util.Set; 97 import java.util.concurrent.ConcurrentHashMap; 98 import java.util.concurrent.atomic.AtomicBoolean; 99 import java.util.stream.Collectors; 100 101 /** Broadcast Assistant Scan Service */ 102 public class BassClientService extends ProfileService { 103 static final String TAG = BassClientService.class.getSimpleName(); 104 105 private static final int MAX_ACTIVE_SYNCED_SOURCES_NUM = 4; 106 private static final int MAX_BIS_DISCOVERY_TRIES_NUM = 5; 107 108 private static final int STATUS_LOCAL_STREAM_REQUESTED = 0; 109 private static final int STATUS_LOCAL_STREAM_STREAMING = 1; 110 private static final int STATUS_LOCAL_STREAM_SUSPENDED = 2; 111 private static final int STATUS_LOCAL_STREAM_REQUESTED_NO_CONTEXT_VALIDATE = 3; 112 113 // Do not modify without updating the HAL bt_le_audio.h files. 114 // Match up with BroadcastState enum of bt_le_audio.h 115 private static final int BROADCAST_STATE_STOPPED = 0; 116 private static final int BROADCAST_STATE_CONFIGURING = 1; 117 private static final int BROADCAST_STATE_PAUSED = 2; 118 private static final int BROADCAST_STATE_ENABLING = 3; 119 private static final int BROADCAST_STATE_DISABLING = 4; 120 private static final int BROADCAST_STATE_STOPPING = 5; 121 private static final int BROADCAST_STATE_STREAMING = 6; 122 123 @VisibleForTesting static final int MESSAGE_SYNC_TIMEOUT = 1; 124 @VisibleForTesting static final int MESSAGE_BIG_MONITOR_TIMEOUT = 2; 125 @VisibleForTesting static final int MESSAGE_BROADCAST_MONITOR_TIMEOUT = 3; 126 @VisibleForTesting static final int MESSAGE_SYNC_LOST_TIMEOUT = 4; 127 128 /* 1 minute timeout for primary device reconnection in Private Broadcast case */ 129 private static final int DIALING_OUT_TIMEOUT_MS = 60000; 130 131 // 30 secs timeout for keeping PSYNC active when searching is stopped 132 private static final Duration sSyncActiveTimeout = Duration.ofSeconds(30); 133 134 // 30 minutes timeout for monitoring BIG resynchronization 135 private static final Duration sBigMonitorTimeout = Duration.ofMinutes(30); 136 137 // 5 minutes timeout for monitoring broadcaster 138 private static final Duration sBroadcasterMonitorTimeout = Duration.ofMinutes(5); 139 140 // 5 seconds timeout for sync Lost notification 141 private static final Duration sSyncLostTimeout = Duration.ofSeconds(5); 142 143 private enum PauseType { 144 HOST_INTENTIONAL, 145 SINK_UNINTENTIONAL 146 } 147 148 private static BassClientService sService; 149 150 private final Map<BluetoothDevice, BassClientStateMachine> mStateMachines = new HashMap<>(); 151 private final Object mSearchScanCallbackLock = new Object(); 152 private final Map<Integer, ScanResult> mCachedBroadcasts = new HashMap<>(); 153 154 private final List<Integer> mActiveSyncedSources = new ArrayList<>(); 155 private final Map<Integer, PeriodicAdvertisingCallback> mPeriodicAdvCallbacksMap = 156 new HashMap<>(); 157 private final PriorityQueue<SourceSyncRequest> mSourceSyncRequestsQueue = 158 new PriorityQueue<>(sSourceSyncRequestComparator); 159 private final Map<Integer, Integer> mSyncFailureCounter = new HashMap<>(); 160 private final Map<Integer, Integer> mBisDiscoveryCounterMap = new HashMap<>(); 161 private final List<AddSourceData> mPendingSourcesToAdd = new ArrayList<>(); 162 163 private final Map<BluetoothDevice, List<Pair<Integer, Object>>> mPendingGroupOp = 164 new ConcurrentHashMap<>(); 165 private final Map<BluetoothDevice, List<Integer>> mGroupManagedSources = 166 new ConcurrentHashMap<>(); 167 private final Map<BluetoothDevice, List<Integer>> mActiveSourceMap = new ConcurrentHashMap<>(); 168 private final Map<BluetoothDevice, Map<Integer, BluetoothLeBroadcastMetadata>> 169 mBroadcastMetadataMap = new ConcurrentHashMap<>(); 170 private final Set<BluetoothDevice> mPausedBroadcastSinks = ConcurrentHashMap.newKeySet(); 171 private final Map<BluetoothDevice, Pair<Integer, Integer>> mSinksWaitingForPast = 172 new HashMap<>(); 173 private final Map<Integer, PauseType> mPausedBroadcastIds = new HashMap<>(); 174 private final Map<Integer, HashSet<BluetoothDevice>> mLocalBroadcastReceivers = 175 new ConcurrentHashMap<>(); 176 private final BassScanCallbackWrapper mBassScanCallback = new BassScanCallbackWrapper(); 177 178 private final AdapterService mAdapterService; 179 private final DatabaseManager mDatabaseManager; 180 private final BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 181 private final HandlerThread mStateMachinesThread; 182 private final HandlerThread mCallbackHandlerThread; 183 private final Callbacks mCallbacks; 184 185 private BluetoothLeScannerWrapper mBluetoothLeScannerWrapper = null; 186 private DialingOutTimeoutEvent mDialingOutTimeoutEvent = null; 187 188 /* Caching the PeriodicAdvertisementResult from Broadcast source */ 189 /* This is stored at service so that each device state machine can access 190 and use it as needed. Once the periodic sync in cancelled, this data will be 191 removed to ensure stable data won't used */ 192 /* syncHandle, broadcastSrcDevice */ 193 private final Map<Integer, BluetoothDevice> mSyncHandleToDeviceMap = new HashMap<>(); 194 /*syncHandle, parsed BaseData data*/ 195 private final Map<Integer, BaseData> mSyncHandleToBaseDataMap = new HashMap<>(); 196 /*syncHandle, broadcast id */ 197 private final Map<Integer, Integer> mSyncHandleToBroadcastIdMap = new HashMap<>(); 198 /*bcastSrcDevice, corresponding broadcast id and PeriodicAdvertisementResult*/ 199 private final Map<BluetoothDevice, HashMap<Integer, PeriodicAdvertisementResult>> 200 mPeriodicAdvertisementResultMap = new HashMap<>(); 201 private ScanCallback mSearchScanCallback = null; 202 private boolean mIsAssistantActive = false; 203 private boolean mIsAllowedContextOfActiveGroupModified = false; 204 Optional<Integer> mUnicastSourceStreamStatus = Optional.empty(); 205 206 private static final int LOG_NB_EVENTS = 100; 207 private static final BluetoothEventLogger sEventLogger = 208 new BluetoothEventLogger(LOG_NB_EVENTS, TAG + " event log"); 209 210 @VisibleForTesting ServiceFactory mServiceFactory = new ServiceFactory(); 211 212 private class BassScanCallbackWrapper extends IScannerCallback.Stub { 213 private static final int SCANNER_ID_NOT_INITIALIZED = -2; 214 private static final int SCANNER_ID_INITIALIZING = -1; 215 216 private final List<ScanFilter> mBaasUuidFilters = new ArrayList<ScanFilter>(); 217 private int mScannerId = SCANNER_ID_NOT_INITIALIZED; 218 registerAndStartScan(List<ScanFilter> filters)219 void registerAndStartScan(List<ScanFilter> filters) { 220 synchronized (this) { 221 if (mScannerId == SCANNER_ID_INITIALIZING) { 222 Log.d(TAG, "registerAndStartScan: Scanner is already initializing"); 223 mCallbacks.notifySearchStartFailed( 224 BluetoothStatusCodes.ERROR_ALREADY_IN_TARGET_STATE); 225 return; 226 } 227 ScanController controller = mAdapterService.getBluetoothScanController(); 228 if (controller == null) { 229 Log.d(TAG, "registerAndStartScan: ScanController is null"); 230 mCallbacks.notifySearchStartFailed(BluetoothStatusCodes.ERROR_UNKNOWN); 231 return; 232 } 233 if (filters != null) { 234 mBaasUuidFilters.addAll(filters); 235 } 236 237 if (!BassUtils.containUuid(mBaasUuidFilters, BassConstants.BAAS_UUID)) { 238 byte[] serviceData = {0x00, 0x00, 0x00}; // Broadcast_ID 239 byte[] serviceDataMask = {0x00, 0x00, 0x00}; 240 241 mBaasUuidFilters.add( 242 new ScanFilter.Builder() 243 .setServiceData( 244 BassConstants.BAAS_UUID, serviceData, serviceDataMask) 245 .build()); 246 } 247 248 mScannerId = SCANNER_ID_INITIALIZING; 249 controller.registerScannerInternal(this, getAttributionSource(), null); 250 } 251 } 252 stopScanAndUnregister()253 void stopScanAndUnregister() { 254 synchronized (this) { 255 ScanController controller = mAdapterService.getBluetoothScanController(); 256 if (controller == null) { 257 Log.d(TAG, "stopScanAndUnregister: ScanController is null"); 258 mCallbacks.notifySearchStopFailed(BluetoothStatusCodes.ERROR_UNKNOWN); 259 return; 260 } 261 controller.stopScanInternal(mScannerId); 262 controller.unregisterScannerInternal(mScannerId); 263 mBaasUuidFilters.clear(); 264 mScannerId = SCANNER_ID_NOT_INITIALIZED; 265 } 266 } 267 isBroadcastAudioAnnouncementScanActive()268 boolean isBroadcastAudioAnnouncementScanActive() { 269 synchronized (this) { 270 return mScannerId >= 0; 271 } 272 } 273 274 @Override onScannerRegistered(int status, int scannerId)275 public void onScannerRegistered(int status, int scannerId) { 276 Log.d(TAG, "onScannerRegistered: Status: " + status + ", id:" + scannerId); 277 synchronized (this) { 278 if (status != BluetoothStatusCodes.SUCCESS) { 279 Log.e(TAG, "onScannerRegistered: Scanner registration failed: " + status); 280 mCallbacks.notifySearchStartFailed(BluetoothStatusCodes.ERROR_UNKNOWN); 281 mScannerId = SCANNER_ID_NOT_INITIALIZED; 282 return; 283 } 284 mScannerId = scannerId; 285 286 ScanSettings settings = 287 new ScanSettings.Builder() 288 .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES) 289 .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) 290 .setLegacy(false) 291 .build(); 292 293 ScanController controller = mAdapterService.getBluetoothScanController(); 294 if (controller == null) { 295 Log.d(TAG, "onScannerRegistered: ScanController is null"); 296 mCallbacks.notifySearchStartFailed(BluetoothStatusCodes.ERROR_UNKNOWN); 297 return; 298 } 299 controller.startScanInternal(scannerId, settings, mBaasUuidFilters); 300 mCallbacks.notifySearchStarted(BluetoothStatusCodes.REASON_LOCAL_APP_REQUEST); 301 } 302 } 303 304 @Override onScanResult(ScanResult result)305 public void onScanResult(ScanResult result) { 306 log("onScanResult:" + result); 307 synchronized (this) { 308 if (mScannerId < 0) { 309 Log.d(TAG, "onScanResult: Ignoring result as scan stopped."); 310 return; 311 } 312 } 313 314 Integer broadcastId = BassUtils.getBroadcastId(result); 315 if (broadcastId == BassConstants.INVALID_BROADCAST_ID) { 316 Log.d(TAG, "onScanResult: Broadcast ID is invalid"); 317 return; 318 } 319 320 log("Broadcast Source Found:" + result.getDevice()); 321 sEventLogger.logd(TAG, "Broadcast Source Found: Broadcast ID: " + broadcastId); 322 323 synchronized (mSearchScanCallbackLock) { 324 if (!mCachedBroadcasts.containsKey(broadcastId)) { 325 log("selectBroadcastSource: broadcastId " + broadcastId); 326 mCachedBroadcasts.put(broadcastId, result); 327 addSelectSourceRequest(broadcastId, /* hasPriority */ false); 328 } else { 329 if (mTimeoutHandler.isStarted(broadcastId, MESSAGE_SYNC_LOST_TIMEOUT)) { 330 mTimeoutHandler.stop(broadcastId, MESSAGE_SYNC_LOST_TIMEOUT); 331 mTimeoutHandler.start( 332 broadcastId, MESSAGE_SYNC_LOST_TIMEOUT, sSyncLostTimeout); 333 } 334 if (isSinkUnintentionalPauseType(broadcastId)) { 335 addSelectSourceRequest(broadcastId, /* hasPriority */ true); 336 } 337 } 338 } 339 } 340 341 @Override onBatchScanResults(List<ScanResult> batchResults)342 public void onBatchScanResults(List<ScanResult> batchResults) {} 343 344 @Override onFoundOrLost(boolean onFound, ScanResult scanResult)345 public void onFoundOrLost(boolean onFound, ScanResult scanResult) {} 346 347 @Override onScanManagerErrorCallback(int errorCode)348 public void onScanManagerErrorCallback(int errorCode) { 349 Log.d(TAG, "onScanManagerErrorCallback: errorCode = " + errorCode); 350 synchronized (this) { 351 if (mScannerId < 0) { 352 return; 353 } 354 } 355 mScannerId = SCANNER_ID_NOT_INITIALIZED; 356 informConnectedDeviceAboutScanOffloadStop(); 357 } 358 } 359 360 @VisibleForTesting 361 final Handler mHandler = 362 new Handler(Looper.getMainLooper()) { 363 @Override 364 public void handleMessage(Message msg) { 365 switch (msg.what) { 366 case MESSAGE_SYNC_TIMEOUT: 367 { 368 log("MESSAGE_SYNC_TIMEOUT"); 369 clearAllSyncData(); 370 break; 371 } 372 default: 373 break; 374 } 375 } 376 }; 377 378 @VisibleForTesting public final TimeoutHandler mTimeoutHandler = new TimeoutHandler(); 379 380 @VisibleForTesting 381 public final class TimeoutHandler { 382 private final Map<Integer, Handler> mHandlers = new HashMap<>(); 383 384 @VisibleForTesting getOrCreateHandler(int broadcastId)385 public Handler getOrCreateHandler(int broadcastId) { 386 return mHandlers.computeIfAbsent( 387 broadcastId, 388 key -> 389 new Handler(Looper.getMainLooper()) { 390 @Override 391 public void handleMessage(Message msg) { 392 switch (msg.what) { 393 case MESSAGE_SYNC_LOST_TIMEOUT: 394 { 395 log("MESSAGE_SYNC_LOST_TIMEOUT"); 396 // fall through 397 } 398 case MESSAGE_BROADCAST_MONITOR_TIMEOUT: 399 { 400 log("MESSAGE_BROADCAST_MONITOR_TIMEOUT"); 401 if (getActiveSyncedSources() 402 .contains( 403 getSyncHandleForBroadcastId( 404 broadcastId))) { 405 break; 406 } 407 // Clear from cache to make possible sync again 408 // (only during active searching) 409 synchronized (mSearchScanCallbackLock) { 410 if (isSearchInProgress()) { 411 mCachedBroadcasts.remove(broadcastId); 412 } 413 } 414 log( 415 "Notify broadcast source lost, broadcast" 416 + " id: " 417 + broadcastId); 418 mCallbacks.notifySourceLost(broadcastId); 419 if (!isSinkUnintentionalPauseType(broadcastId)) { 420 break; 421 } 422 // fall through 423 } 424 case MESSAGE_BIG_MONITOR_TIMEOUT: 425 { 426 log("MESSAGE_BIG_MONITOR_TIMEOUT"); 427 stopSourceReceivers(broadcastId); 428 break; 429 } 430 default: 431 break; 432 } 433 Handler handler = getOrCreateHandler(broadcastId); 434 if (!hasAnyMessagesOrCallbacks(handler)) { 435 mHandlers.remove(broadcastId); 436 } 437 } 438 }); 439 } 440 441 void start(int broadcastId, int msg, Duration duration) { 442 Handler handler = getOrCreateHandler(broadcastId); 443 log( 444 "Started timeout: " 445 + ("broadcastId: " + broadcastId) 446 + (", msg: " + msg) 447 + (", duration: " + duration)); 448 handler.sendEmptyMessageDelayed(msg, duration.toMillis()); 449 } 450 451 void stop(int broadcastId, int msg) { 452 if (!mHandlers.containsKey(broadcastId)) { 453 return; 454 } 455 Handler handler = getOrCreateHandler(broadcastId); 456 handler.removeMessages(msg); 457 if (!hasAnyMessagesOrCallbacks(handler)) { 458 mHandlers.remove(broadcastId); 459 } 460 } 461 462 void stopAll() { 463 for (Handler handler : mHandlers.values()) { 464 handler.removeCallbacksAndMessages(null); 465 } 466 mHandlers.clear(); 467 } 468 469 void stopAll(int msg) { 470 Iterator<Map.Entry<Integer, Handler>> iterator = mHandlers.entrySet().iterator(); 471 while (iterator.hasNext()) { 472 Map.Entry<Integer, Handler> entry = iterator.next(); 473 Handler handler = entry.getValue(); 474 handler.removeMessages(msg); 475 if (!hasAnyMessagesOrCallbacks(handler)) { 476 iterator.remove(); 477 } 478 } 479 } 480 481 boolean isStarted(int broadcastId, int msg) { 482 if (!mHandlers.containsKey(broadcastId)) { 483 return false; 484 } 485 Handler handler = getOrCreateHandler(broadcastId); 486 return handler.hasMessages(msg); 487 } 488 489 @SuppressLint("NewApi") // Api is protected by flag check and the lint is wrong 490 private static boolean hasAnyMessagesOrCallbacks(Handler handler) { 491 if (android.os.Flags.mainlineVcnPlatformApi()) { 492 return handler.hasMessagesOrCallbacks(); 493 } else { 494 return handler.hasMessages(MESSAGE_SYNC_LOST_TIMEOUT) 495 || handler.hasMessages(MESSAGE_BROADCAST_MONITOR_TIMEOUT) 496 || handler.hasMessages(MESSAGE_BIG_MONITOR_TIMEOUT); 497 } 498 } 499 } 500 501 public BassClientService(AdapterService adapterService) { 502 super(requireNonNull(adapterService)); 503 mAdapterService = adapterService; 504 mDatabaseManager = requireNonNull(mAdapterService.getDatabase()); 505 requireNonNull(mBluetoothAdapter); 506 507 mStateMachinesThread = new HandlerThread("BassClientService.StateMachines"); 508 mStateMachinesThread.start(); 509 mCallbackHandlerThread = new HandlerThread(TAG); 510 mCallbackHandlerThread.start(); 511 mCallbacks = new Callbacks(mCallbackHandlerThread.getLooper()); 512 513 setBassClientService(this); 514 } 515 516 public static boolean isEnabled() { 517 return BluetoothProperties.isProfileBapBroadcastAssistEnabled().orElse(false); 518 } 519 520 private record SourceSyncRequest( 521 ScanResult scanResult, boolean hasPriority, int syncFailureCounter) { 522 523 public int getRssi() { 524 return scanResult.getRssi(); 525 } 526 527 @Override 528 public String toString() { 529 return "SourceSyncRequest{" 530 + "scanResult=" 531 + scanResult 532 + ", hasPriority=" 533 + hasPriority 534 + ", syncFailureCounter=" 535 + syncFailureCounter 536 + '}'; 537 } 538 } 539 540 private static final Comparator<SourceSyncRequest> sSourceSyncRequestComparator = 541 new Comparator<SourceSyncRequest>() { 542 @Override 543 public int compare(SourceSyncRequest ssr1, SourceSyncRequest ssr2) { 544 if (ssr1.hasPriority && !ssr2.hasPriority) { 545 return -1; 546 } else if (!ssr1.hasPriority && ssr2.hasPriority) { 547 return 1; 548 } else if (leaudioSortScansToSyncByFails() 549 && (ssr1.syncFailureCounter != ssr2.syncFailureCounter)) { 550 return Integer.compare(ssr1.syncFailureCounter, ssr2.syncFailureCounter); 551 } else { 552 return Integer.compare(ssr2.getRssi(), ssr1.getRssi()); 553 } 554 } 555 }; 556 557 private record AddSourceData( 558 BluetoothDevice sink, BluetoothLeBroadcastMetadata sourceMetadata, boolean isGroupOp) {} 559 560 void updatePeriodicAdvertisementResultMap( 561 BluetoothDevice device, 562 int addressType, 563 int syncHandle, 564 int advSid, 565 int advInterval, 566 int bId, 567 PublicBroadcastData pbData, 568 String broadcastName) { 569 log("updatePeriodicAdvertisementResultMap: device: " + device); 570 log("updatePeriodicAdvertisementResultMap: syncHandle: " + syncHandle); 571 log("updatePeriodicAdvertisementResultMap: advSid: " + advSid); 572 log("updatePeriodicAdvertisementResultMap: addressType: " + addressType); 573 log("updatePeriodicAdvertisementResultMap: advInterval: " + advInterval); 574 log("updatePeriodicAdvertisementResultMap: broadcastId: " + bId); 575 log("updatePeriodicAdvertisementResultMap: broadcastName: " + broadcastName); 576 log("mSyncHandleToDeviceMap" + mSyncHandleToDeviceMap); 577 log("mPeriodicAdvertisementResultMap" + mPeriodicAdvertisementResultMap); 578 HashMap<Integer, PeriodicAdvertisementResult> paResMap = 579 mPeriodicAdvertisementResultMap.get(device); 580 if (paResMap == null 581 || (bId != BassConstants.INVALID_BROADCAST_ID && !paResMap.containsKey(bId))) { 582 log("PAResmap: add >>>"); 583 mSyncHandleToDeviceMap.put(syncHandle, device); 584 updateSyncHandleForBroadcastId(syncHandle, bId); 585 PeriodicAdvertisementResult paRes = 586 new PeriodicAdvertisementResult( 587 device, 588 addressType, 589 syncHandle, 590 advSid, 591 advInterval, 592 bId, 593 pbData, 594 broadcastName); 595 if (paRes != null) { 596 paRes.print(); 597 mPeriodicAdvertisementResultMap.putIfAbsent(device, new HashMap<>()); 598 mPeriodicAdvertisementResultMap.get(device).put(bId, paRes); 599 } 600 } else { 601 log("PAResmap: update >>>"); 602 if (bId == BassConstants.INVALID_BROADCAST_ID) { 603 // Update when onSyncEstablished, try to retrieve valid broadcast id 604 bId = getBroadcastIdForSyncHandle(BassConstants.PENDING_SYNC_HANDLE); 605 606 if (bId == BassConstants.INVALID_BROADCAST_ID || !paResMap.containsKey(bId)) { 607 Log.e(TAG, "PAResmap: error! no valid broadcast id found>>>"); 608 return; 609 } 610 611 int oldBroadcastId = getBroadcastIdForSyncHandle(syncHandle); 612 if (oldBroadcastId != BassConstants.INVALID_BROADCAST_ID && oldBroadcastId != bId) { 613 log( 614 "updatePeriodicAdvertisementResultMap: SyncEstablished on the" 615 + " same syncHandle=" 616 + syncHandle 617 + ", before syncLost"); 618 log("Notify broadcast source lost, broadcast id: " + oldBroadcastId); 619 mCallbacks.notifySourceLost(oldBroadcastId); 620 clearAllDataForSyncHandle(syncHandle); 621 mCachedBroadcasts.remove(oldBroadcastId); 622 } 623 } 624 PeriodicAdvertisementResult paRes = paResMap.get(bId); 625 if (advSid != BassConstants.INVALID_ADV_SID) { 626 paRes.updateAdvSid(advSid); 627 } 628 if (syncHandle != BassConstants.INVALID_SYNC_HANDLE 629 && syncHandle != BassConstants.PENDING_SYNC_HANDLE) { 630 mSyncHandleToDeviceMap 631 .entrySet() 632 .removeIf(entry -> entry.getValue().equals(device)); 633 mSyncHandleToDeviceMap.put(syncHandle, device); 634 paRes.updateSyncHandle(syncHandle); 635 if (paRes.getBroadcastId() != BassConstants.INVALID_BROADCAST_ID) { 636 // broadcast successfully synced 637 // update the sync handle for the broadcast source 638 updateSyncHandleForBroadcastId(syncHandle, paRes.getBroadcastId()); 639 } 640 } 641 if (addressType != BassConstants.INVALID_ADV_ADDRESS_TYPE) { 642 paRes.updateAddressType(addressType); 643 } 644 if (advInterval != BassConstants.INVALID_ADV_INTERVAL) { 645 paRes.updateAdvInterval(advInterval); 646 } 647 if (bId != BassConstants.INVALID_BROADCAST_ID) { 648 paRes.updateBroadcastId(bId); 649 } 650 if (pbData != null) { 651 paRes.updatePublicBroadcastData(pbData); 652 } 653 if (broadcastName != null) { 654 paRes.updateBroadcastName(broadcastName); 655 } 656 paRes.print(); 657 paResMap.replace(bId, paRes); 658 } 659 log(">>mPeriodicAdvertisementResultMap" + mPeriodicAdvertisementResultMap); 660 } 661 662 PeriodicAdvertisementResult getPeriodicAdvertisementResult( 663 BluetoothDevice device, int broadcastId) { 664 if (broadcastId == BassConstants.INVALID_BROADCAST_ID) { 665 Log.e(TAG, "getPeriodicAdvertisementResult: invalid broadcast id"); 666 return null; 667 } 668 669 if (mPeriodicAdvertisementResultMap.containsKey(device)) { 670 return mPeriodicAdvertisementResultMap.get(device).get(broadcastId); 671 } 672 return null; 673 } 674 675 void clearNotifiedFlags() { 676 log("clearNotifiedFlags"); 677 for (Map.Entry<BluetoothDevice, HashMap<Integer, PeriodicAdvertisementResult>> entry : 678 mPeriodicAdvertisementResultMap.entrySet()) { 679 HashMap<Integer, PeriodicAdvertisementResult> value = entry.getValue(); 680 for (PeriodicAdvertisementResult result : value.values()) { 681 result.setNotified(false); 682 result.print(); 683 } 684 } 685 } 686 687 void updateBase(int syncHandleMap, BaseData base) { 688 log("updateBase : mSyncHandleToBaseDataMap>>"); 689 mSyncHandleToBaseDataMap.put(syncHandleMap, base); 690 } 691 692 BaseData getBase(int syncHandleMap) { 693 BaseData base = mSyncHandleToBaseDataMap.get(syncHandleMap); 694 log("getBase returns " + base); 695 return base; 696 } 697 698 void removeActiveSyncedSource(Integer syncHandle) { 699 log("removeActiveSyncedSource, syncHandle: " + syncHandle); 700 if (syncHandle == null) { 701 // remove all sources 702 mActiveSyncedSources.clear(); 703 } else { 704 mActiveSyncedSources.removeIf(e -> e.equals(syncHandle)); 705 } 706 sEventLogger.logd(TAG, "Broadcast Source Unsynced: syncHandle= " + syncHandle); 707 } 708 709 void addActiveSyncedSource(Integer syncHandle) { 710 log("addActiveSyncedSource, syncHandle: " + syncHandle); 711 if (syncHandle != BassConstants.INVALID_SYNC_HANDLE) { 712 if (!mActiveSyncedSources.contains(syncHandle)) { 713 mActiveSyncedSources.add(syncHandle); 714 } 715 } 716 sEventLogger.logd(TAG, "Broadcast Source Synced: syncHandle= " + syncHandle); 717 } 718 719 List<Integer> getActiveSyncedSources() { 720 log("getActiveSyncedSources: sources num: " + mActiveSyncedSources.size()); 721 return mActiveSyncedSources; 722 } 723 724 ScanResult getCachedBroadcast(int broadcastId) { 725 return mCachedBroadcasts.get(broadcastId); 726 } 727 728 public Callbacks getCallbacks() { 729 return mCallbacks; 730 } 731 732 @Override 733 protected IProfileServiceBinder initBinder() { 734 return new BassClientServiceBinder(this); 735 } 736 737 @Override 738 @SuppressLint("AndroidFrameworkRequiresPermission") // TODO: b/350563786 - Fix BASS annotation 739 public void cleanup() { 740 Log.i(TAG, "Cleanup BassClient Service"); 741 742 mUnicastSourceStreamStatus = Optional.empty(); 743 744 if (mDialingOutTimeoutEvent != null) { 745 mHandler.removeCallbacks(mDialingOutTimeoutEvent); 746 mDialingOutTimeoutEvent = null; 747 } 748 749 if (mIsAssistantActive) { 750 LeAudioService leAudioService = mServiceFactory.getLeAudioService(); 751 if (leAudioService != null) { 752 leAudioService.activeBroadcastAssistantNotification(false); 753 } 754 mIsAssistantActive = false; 755 } 756 757 if (mIsAllowedContextOfActiveGroupModified) { 758 LeAudioService leAudioService = mServiceFactory.getLeAudioService(); 759 if (leAudioService != null) { 760 leAudioService.setActiveGroupAllowedContextMask( 761 BluetoothLeAudio.CONTEXTS_ALL, BluetoothLeAudio.CONTEXTS_ALL); 762 } 763 mIsAllowedContextOfActiveGroupModified = false; 764 } 765 766 synchronized (mStateMachines) { 767 for (BassClientStateMachine sm : mStateMachines.values()) { 768 BassObjectsFactory.getInstance().destroyStateMachine(sm); 769 } 770 mStateMachines.clear(); 771 } 772 mCallbackHandlerThread.quitSafely(); 773 mStateMachinesThread.quitSafely(); 774 775 mHandler.removeCallbacksAndMessages(null); 776 mTimeoutHandler.stopAll(); 777 778 setBassClientService(null); 779 synchronized (mSearchScanCallbackLock) { 780 if (leaudioBassScanWithInternalScanController()) { 781 if (isSearchInProgress()) { 782 mBassScanCallback.stopScanAndUnregister(); 783 } 784 } else { 785 if (mBluetoothLeScannerWrapper != null && mSearchScanCallback != null) { 786 mBluetoothLeScannerWrapper.stopScan(mSearchScanCallback); 787 } 788 mBluetoothLeScannerWrapper = null; 789 mSearchScanCallback = null; 790 } 791 clearAllSyncData(); 792 } 793 794 mLocalBroadcastReceivers.clear(); 795 mPendingGroupOp.clear(); 796 mBroadcastMetadataMap.clear(); 797 mPausedBroadcastSinks.clear(); 798 } 799 800 BluetoothDevice getDeviceForSyncHandle(int syncHandle) { 801 return mSyncHandleToDeviceMap.get(syncHandle); 802 } 803 804 Integer getSyncHandleForBroadcastId(int broadcastId) { 805 Integer syncHandle = BassConstants.INVALID_SYNC_HANDLE; 806 for (Map.Entry<Integer, Integer> entry : mSyncHandleToBroadcastIdMap.entrySet()) { 807 Integer value = entry.getValue(); 808 if (value == broadcastId) { 809 syncHandle = entry.getKey(); 810 break; 811 } 812 } 813 return syncHandle; 814 } 815 816 Integer getBroadcastIdForSyncHandle(int syncHandle) { 817 if (mSyncHandleToBroadcastIdMap.containsKey(syncHandle)) { 818 return mSyncHandleToBroadcastIdMap.get(syncHandle); 819 } 820 return BassConstants.INVALID_BROADCAST_ID; 821 } 822 823 void updateSyncHandleForBroadcastId(int syncHandle, int broadcastId) { 824 mSyncHandleToBroadcastIdMap.entrySet().removeIf(entry -> entry.getValue() == broadcastId); 825 mSyncHandleToBroadcastIdMap.put(syncHandle, broadcastId); 826 log("Updated mSyncHandleToBroadcastIdMap: " + mSyncHandleToBroadcastIdMap); 827 } 828 829 private static synchronized void setBassClientService(BassClientService instance) { 830 Log.d(TAG, "setBassClientService(): set to: " + instance); 831 sService = instance; 832 } 833 834 private void enqueueSourceGroupOp(BluetoothDevice sink, Integer msgId, Object obj) { 835 log("enqueueSourceGroupOp device: " + sink + ", msgId: " + msgId); 836 837 mPendingGroupOp.compute( 838 sink, 839 (key, opsToModify) -> { 840 List<Pair<Integer, Object>> operations = 841 (opsToModify == null) 842 ? new ArrayList<>() 843 : new ArrayList<>(opsToModify); 844 operations.add(new Pair<>(msgId, obj)); 845 return operations; 846 }); 847 } 848 849 private static boolean isSuccess(int status) { 850 boolean ret = false; 851 switch (status) { 852 case BluetoothStatusCodes.REASON_LOCAL_APP_REQUEST: 853 case BluetoothStatusCodes.REASON_LOCAL_STACK_REQUEST: 854 case BluetoothStatusCodes.REASON_REMOTE_REQUEST: 855 case BluetoothStatusCodes.REASON_SYSTEM_POLICY: 856 ret = true; 857 break; 858 default: 859 break; 860 } 861 return ret; 862 } 863 864 private boolean isAnyPendingAddSourceOperation() { 865 for (BluetoothDevice device : getConnectedDevices()) { 866 List<Pair<Integer, Object>> operations = mPendingGroupOp.get(device); 867 if (operations == null) { 868 continue; 869 } 870 871 boolean isAnyPendingAddSourceOperationForDevice = 872 operations.stream() 873 .anyMatch(e -> e.first.equals(BassClientStateMachine.ADD_BCAST_SOURCE)); 874 875 if (isAnyPendingAddSourceOperationForDevice) { 876 return true; 877 } 878 } 879 880 return false; 881 } 882 883 private void checkForPendingGroupOpRequest( 884 BluetoothDevice sink, int reason, int reqMsg, Object obj) { 885 log( 886 "checkForPendingGroupOpRequest device: " 887 + sink 888 + ", reason: " 889 + reason 890 + ", reqMsg: " 891 + reqMsg); 892 893 AtomicBoolean shouldUpdateAssistantActive = new AtomicBoolean(false); 894 895 mPendingGroupOp.computeIfPresent( 896 sink, 897 (key, opsToModify) -> { 898 List<Pair<Integer, Object>> operations = new ArrayList<>(opsToModify); 899 900 switch (reqMsg) { 901 case BassClientStateMachine.ADD_BCAST_SOURCE: 902 if (obj == null) { 903 return operations; 904 } 905 // Identify the operation by operation type and broadcastId 906 if (isSuccess(reason)) { 907 BluetoothLeBroadcastReceiveState sourceState = 908 (BluetoothLeBroadcastReceiveState) obj; 909 if (removeMatchingOperation(operations, reqMsg, obj)) { 910 setSourceGroupManaged(sink, sourceState.getSourceId(), true); 911 } 912 } else { 913 removeMatchingOperation(operations, reqMsg, obj); 914 shouldUpdateAssistantActive.set(true); 915 } 916 break; 917 case BassClientStateMachine.REMOVE_BCAST_SOURCE: 918 // Identify the operation by operation type and sourceId 919 removeMatchingOperation(operations, reqMsg, obj); 920 Integer sourceId = (Integer) obj; 921 setSourceGroupManaged(sink, sourceId, false); 922 break; 923 default: 924 break; 925 } 926 return operations; 927 }); 928 929 if (shouldUpdateAssistantActive.get() 930 && !isAnyPendingAddSourceOperation() 931 && mIsAssistantActive 932 && mPausedBroadcastSinks.isEmpty()) { 933 LeAudioService leAudioService = mServiceFactory.getLeAudioService(); 934 mIsAssistantActive = false; 935 mUnicastSourceStreamStatus = Optional.empty(); 936 937 if (leAudioService != null) { 938 leAudioService.activeBroadcastAssistantNotification(false); 939 } 940 } 941 } 942 943 private static boolean removeMatchingOperation( 944 List<Pair<Integer, Object>> operations, int reqMsg, Object obj) { 945 return operations.removeIf( 946 m -> m.first.equals(reqMsg) && isMatchingOperation(m.second, obj)); 947 } 948 949 private static boolean isMatchingOperation(Object operationData, Object obj) { 950 if (obj instanceof BluetoothLeBroadcastReceiveState) { 951 return ((BluetoothLeBroadcastMetadata) operationData).getBroadcastId() 952 == ((BluetoothLeBroadcastReceiveState) obj).getBroadcastId(); 953 } else if (obj instanceof BluetoothLeBroadcastMetadata) { 954 return ((BluetoothLeBroadcastMetadata) operationData).getBroadcastId() 955 == ((BluetoothLeBroadcastMetadata) obj).getBroadcastId(); 956 } else if (obj instanceof Integer) { 957 return obj.equals(operationData); 958 } 959 return false; 960 } 961 962 private boolean isDevicePartOfActiveUnicastGroup(BluetoothDevice device) { 963 LeAudioService leAudioService = mServiceFactory.getLeAudioService(); 964 if (leAudioService == null) { 965 return false; 966 } 967 968 return (leAudioService.getActiveGroupId() != LE_AUDIO_GROUP_ID_INVALID) 969 && (leAudioService.getActiveDevices().contains(device)); 970 } 971 972 private static boolean isEmptyBluetoothDevice(BluetoothDevice device) { 973 if (device == null) { 974 Log.e(TAG, "Device is null!"); 975 return true; 976 } 977 978 return device.getAddress().equals("00:00:00:00:00:00"); 979 } 980 981 private boolean hasAnyConnectedDeviceExternalBroadcastSource() { 982 for (BluetoothDevice device : getConnectedDevices()) { 983 // Check if any connected device has add some source 984 if (getAllSources(device).stream() 985 .anyMatch(receiveState -> (!isLocalBroadcast(receiveState)))) { 986 return true; 987 } 988 } 989 990 return false; 991 } 992 993 private boolean isAnyConnectedDeviceSwitchingSource() { 994 for (BluetoothDevice device : getConnectedDevices()) { 995 synchronized (mStateMachines) { 996 BassClientStateMachine sm = getOrCreateStateMachine(device); 997 // Need to check both mPendingSourceToSwitch and mPendingMetadata 998 // to guard the whole source switching flow 999 if (sm != null 1000 && (sm.hasPendingSwitchingSourceOperation() 1001 || sm.hasPendingSourceOperation())) { 1002 return true; 1003 } 1004 } 1005 } 1006 return false; 1007 } 1008 1009 private void checkAndSetGroupAllowedContextMask(BluetoothDevice sink) { 1010 LeAudioService leAudioService = mServiceFactory.getLeAudioService(); 1011 if (leAudioService == null) { 1012 return; 1013 } 1014 1015 /* Don't bother active group (external broadcaster scenario) with SOUND EFFECTS */ 1016 if (!mIsAllowedContextOfActiveGroupModified && isDevicePartOfActiveUnicastGroup(sink)) { 1017 leAudioService.setActiveGroupAllowedContextMask( 1018 BluetoothLeAudio.CONTEXTS_ALL & ~BluetoothLeAudio.CONTEXT_TYPE_SOUND_EFFECTS, 1019 BluetoothLeAudio.CONTEXTS_ALL); 1020 mIsAllowedContextOfActiveGroupModified = true; 1021 } 1022 } 1023 1024 private void checkAndResetGroupAllowedContextMask() { 1025 LeAudioService leAudioService = mServiceFactory.getLeAudioService(); 1026 if (leAudioService == null) { 1027 return; 1028 } 1029 1030 /* Restore allowed context mask for Unicast */ 1031 if (mIsAllowedContextOfActiveGroupModified 1032 && !hasAnyConnectedDeviceExternalBroadcastSource() 1033 && !isAnyConnectedDeviceSwitchingSource()) { 1034 leAudioService.setActiveGroupAllowedContextMask( 1035 BluetoothLeAudio.CONTEXTS_ALL, BluetoothLeAudio.CONTEXTS_ALL); 1036 mIsAllowedContextOfActiveGroupModified = false; 1037 } 1038 } 1039 1040 void syncRequestForPast(BluetoothDevice sink, int broadcastId, int sourceId) { 1041 log( 1042 "syncRequestForPast sink: " 1043 + sink 1044 + ", broadcastId: " 1045 + broadcastId 1046 + ", sourceId: " 1047 + sourceId); 1048 1049 if (!leaudioBroadcastResyncHelper()) { 1050 return; 1051 } 1052 synchronized (mSinksWaitingForPast) { 1053 mSinksWaitingForPast.put(sink, new Pair<Integer, Integer>(broadcastId, sourceId)); 1054 } 1055 addSelectSourceRequest(broadcastId, /* hasPriority */ true); 1056 } 1057 1058 private void localNotifyReceiveStateChanged( 1059 BluetoothDevice sink, BluetoothLeBroadcastReceiveState receiveState) { 1060 int broadcastId = receiveState.getBroadcastId(); 1061 // If sink has external broadcast synced && not paused by the host 1062 if (leaudioBroadcastResyncHelper() 1063 && !isLocalBroadcast(receiveState) 1064 && !isEmptyBluetoothDevice(receiveState.getSourceDevice()) 1065 && !isHostPauseType(broadcastId)) { 1066 1067 // If sink actively synced (PA or BIG) or waiting for PA 1068 if (isReceiverActive(receiveState) 1069 || receiveState.getPaSyncState() 1070 == BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCINFO_REQUEST) { 1071 // Clear paused broadcast sink (not need to resume manually) 1072 mPausedBroadcastSinks.remove(sink); 1073 1074 // If all sinks for this broadcast are actively synced (PA or BIG) and there is no 1075 // more sinks to resume then stop monitoring 1076 if (isAllReceiversActive(broadcastId) && mPausedBroadcastSinks.isEmpty()) { 1077 stopBigMonitoring(broadcastId, /* hostInitiated */ false); 1078 } 1079 // If broadcast not paused (monitored) yet 1080 } else if (!mPausedBroadcastIds.containsKey(broadcastId)) { 1081 // And BASS has data to start synchronization 1082 if (mCachedBroadcasts.containsKey(broadcastId)) { 1083 // Try to sync to it and start BIG monitoring 1084 mPausedBroadcastIds.put(broadcastId, PauseType.SINK_UNINTENTIONAL); 1085 cacheSuspendingSources(broadcastId); 1086 mTimeoutHandler.stop(broadcastId, MESSAGE_BIG_MONITOR_TIMEOUT); 1087 mTimeoutHandler.start( 1088 broadcastId, MESSAGE_BIG_MONITOR_TIMEOUT, sBigMonitorTimeout); 1089 addSelectSourceRequest(broadcastId, /* hasPriority */ true); 1090 } 1091 } 1092 // If paused by host then stop active sync, it could be not stopped, if during previous 1093 // stop there was pending past request 1094 } else if (leaudioMonitorUnicastSourceWhenManagedByBroadcastDelegator() 1095 && isHostPauseType(broadcastId)) { 1096 stopActiveSync(broadcastId); 1097 // If sink unsynced then remove potentially waiting past and check if any broadcast 1098 // monitoring should be stopped for all broadcast Ids 1099 } else if (isEmptyBluetoothDevice(receiveState.getSourceDevice())) { 1100 synchronized (mSinksWaitingForPast) { 1101 mSinksWaitingForPast.remove(sink); 1102 } 1103 checkAndStopBigMonitoring(); 1104 } 1105 1106 LeAudioService leAudioService = mServiceFactory.getLeAudioService(); 1107 if (leAudioService == null) { 1108 return; 1109 } 1110 1111 boolean isAssistantActive; 1112 if (leaudioMonitorUnicastSourceWhenManagedByBroadcastDelegator()) { 1113 isAssistantActive = hasPrimaryDeviceManagedExternalBroadcast(); 1114 } else { 1115 isAssistantActive = areReceiversReceivingOnlyExternalBroadcast(getConnectedDevices()); 1116 } 1117 1118 if (isAssistantActive) { 1119 /* Assistant become active */ 1120 if (!mIsAssistantActive) { 1121 mIsAssistantActive = true; 1122 leAudioService.activeBroadcastAssistantNotification(true); 1123 } 1124 1125 checkAndSetGroupAllowedContextMask(sink); 1126 } else { 1127 /* Assistant become inactive */ 1128 if (mIsAssistantActive 1129 && mPausedBroadcastSinks.isEmpty() 1130 && !isSinkUnintentionalPauseType(broadcastId)) { 1131 mIsAssistantActive = false; 1132 mUnicastSourceStreamStatus = Optional.empty(); 1133 leAudioService.activeBroadcastAssistantNotification(false); 1134 } 1135 1136 /* Restore allowed context mask for unicast in case if last connected broadcast 1137 * delegator device which has external source removes this source 1138 */ 1139 checkAndResetGroupAllowedContextMask(); 1140 } 1141 } 1142 1143 private void localNotifySourceAdded( 1144 BluetoothDevice sink, BluetoothLeBroadcastReceiveState receiveState) { 1145 if (!isLocalBroadcast(receiveState)) { 1146 return; 1147 } 1148 1149 int broadcastId = receiveState.getBroadcastId(); 1150 1151 /* Track devices bonded to local broadcast for further broadcast status handling when sink 1152 * device is: 1153 * - disconnecting (if no more receivers, broadcast can be stopped) 1154 * - connecting (resynchronize if connection lost) 1155 */ 1156 if (mLocalBroadcastReceivers.containsKey(broadcastId)) { 1157 mLocalBroadcastReceivers.get(broadcastId).add(sink); 1158 } else { 1159 mLocalBroadcastReceivers.put( 1160 broadcastId, new HashSet<BluetoothDevice>(Arrays.asList(sink))); 1161 } 1162 } 1163 1164 private void localNotifySourceAddFailed( 1165 BluetoothDevice sink, BluetoothLeBroadcastMetadata source) { 1166 removeSinkMetadata(sink, source.getBroadcastId()); 1167 } 1168 1169 private void setSourceGroupManaged(BluetoothDevice sink, int sourceId, boolean isGroupOp) { 1170 log("setSourceGroupManaged device: " + sink); 1171 if (isGroupOp) { 1172 if (!mGroupManagedSources.containsKey(sink)) { 1173 mGroupManagedSources.put(sink, new ArrayList<>()); 1174 } 1175 mGroupManagedSources.get(sink).add(sourceId); 1176 } else { 1177 List<Integer> sources = mGroupManagedSources.get(sink); 1178 if (sources != null) { 1179 sources.removeIf(e -> e.equals(sourceId)); 1180 } 1181 } 1182 } 1183 1184 private Pair<BluetoothLeBroadcastMetadata, Map<BluetoothDevice, Integer>> 1185 getGroupManagedDeviceSources(BluetoothDevice sink, Integer sourceId) { 1186 log("getGroupManagedDeviceSources device: " + sink + " sourceId: " + sourceId); 1187 Map map = new HashMap<BluetoothDevice, Integer>(); 1188 1189 if (mGroupManagedSources.containsKey(sink) 1190 && mGroupManagedSources.get(sink).contains(sourceId)) { 1191 BassClientStateMachine stateMachine = getOrCreateStateMachine(sink); 1192 if (stateMachine == null) { 1193 Log.e(TAG, "Can't get state machine for device: " + sink); 1194 return new Pair<BluetoothLeBroadcastMetadata, Map<BluetoothDevice, Integer>>( 1195 null, null); 1196 } 1197 1198 BluetoothLeBroadcastMetadata metadata = 1199 stateMachine.getCurrentBroadcastMetadata(sourceId); 1200 if (metadata != null) { 1201 int broadcastId = metadata.getBroadcastId(); 1202 1203 for (BluetoothDevice device : getTargetDeviceList(sink, /* isGroupOp */ true)) { 1204 List<BluetoothLeBroadcastReceiveState> sources = 1205 getOrCreateStateMachine(device).getAllSources(); 1206 1207 // For each device, find the source ID having this broadcast ID 1208 Optional<BluetoothLeBroadcastReceiveState> receiver = 1209 sources.stream() 1210 .filter(e -> e.getBroadcastId() == broadcastId) 1211 .findAny(); 1212 if (receiver.isPresent()) { 1213 map.put(device, receiver.get().getSourceId()); 1214 } else { 1215 // Put invalid source ID if the remote doesn't have it 1216 map.put(device, BassConstants.INVALID_SOURCE_ID); 1217 } 1218 } 1219 return new Pair<BluetoothLeBroadcastMetadata, Map<BluetoothDevice, Integer>>( 1220 metadata, map); 1221 } else { 1222 Log.e( 1223 TAG, 1224 "Couldn't find broadcast metadata for device: " 1225 + sink 1226 + ", and sourceId:" 1227 + sourceId); 1228 } 1229 } 1230 1231 // Just put this single device if this source is not group managed 1232 map.put(sink, sourceId); 1233 return new Pair<BluetoothLeBroadcastMetadata, Map<BluetoothDevice, Integer>>(null, map); 1234 } 1235 1236 private List<BluetoothDevice> getTargetDeviceList(BluetoothDevice device, boolean isGroupOp) { 1237 if (isGroupOp) { 1238 CsipSetCoordinatorService csipClient = mServiceFactory.getCsipSetCoordinatorService(); 1239 if (csipClient != null) { 1240 // Check for coordinated set of devices in the context of CAP 1241 List<BluetoothDevice> csipDevices = 1242 csipClient.getGroupDevicesOrdered(device, BluetoothUuid.CAP); 1243 if (!csipDevices.isEmpty()) { 1244 return csipDevices; 1245 } else { 1246 Log.w(TAG, "CSIP group is empty."); 1247 } 1248 } else { 1249 Log.e(TAG, "CSIP service is null. No grouping information available."); 1250 } 1251 } 1252 1253 List<BluetoothDevice> devices = new ArrayList<>(); 1254 devices.add(device); 1255 return devices; 1256 } 1257 1258 private int checkDuplicateSourceAdditionAndGetSourceId( 1259 BluetoothDevice device, BluetoothLeBroadcastMetadata metaData) { 1260 int sourceId = BassConstants.INVALID_SOURCE_ID; 1261 List<BluetoothLeBroadcastReceiveState> currentAllSources = getAllSources(device); 1262 for (int i = 0; i < currentAllSources.size(); i++) { 1263 BluetoothLeBroadcastReceiveState state = currentAllSources.get(i); 1264 if (metaData.getSourceDevice().equals(state.getSourceDevice()) 1265 && metaData.getSourceAddressType() == state.getSourceAddressType() 1266 && metaData.getSourceAdvertisingSid() == state.getSourceAdvertisingSid() 1267 && metaData.getBroadcastId() == state.getBroadcastId()) { 1268 sourceId = state.getSourceId(); 1269 log("DuplicatedSourceAddition: for " + device + " metaData: " + metaData); 1270 break; 1271 } 1272 } 1273 return sourceId; 1274 } 1275 1276 private boolean hasRoomForBroadcastSourceAddition(BluetoothDevice device) { 1277 BassClientStateMachine stateMachine = null; 1278 synchronized (mStateMachines) { 1279 stateMachine = getOrCreateStateMachine(device); 1280 } 1281 if (stateMachine == null) { 1282 log("stateMachine is null"); 1283 return false; 1284 } 1285 boolean isRoomAvailable = false; 1286 List<BluetoothLeBroadcastReceiveState> sources = stateMachine.getAllSources(); 1287 if (sources.size() < stateMachine.getMaximumSourceCapacity()) { 1288 isRoomAvailable = true; 1289 } else { 1290 for (BluetoothLeBroadcastReceiveState recvState : sources) { 1291 if (isEmptyBluetoothDevice(recvState.getSourceDevice())) { 1292 isRoomAvailable = true; 1293 break; 1294 } 1295 } 1296 } 1297 log("isRoomAvailable: " + isRoomAvailable); 1298 return isRoomAvailable; 1299 } 1300 1301 private Integer getSourceIdToRemove(BluetoothDevice device) { 1302 BassClientStateMachine stateMachine = null; 1303 1304 synchronized (mStateMachines) { 1305 stateMachine = getOrCreateStateMachine(device); 1306 } 1307 if (stateMachine == null) { 1308 log("stateMachine is null"); 1309 return BassConstants.INVALID_SOURCE_ID; 1310 } 1311 List<BluetoothLeBroadcastReceiveState> sources = stateMachine.getAllSources(); 1312 if (sources.isEmpty()) { 1313 log("sources is empty"); 1314 return BassConstants.INVALID_SOURCE_ID; 1315 } 1316 1317 Integer sourceId = BassConstants.INVALID_SOURCE_ID; 1318 // Select the source by checking if there is one with PA not synced 1319 Optional<BluetoothLeBroadcastReceiveState> receiver = 1320 sources.stream() 1321 .filter( 1322 e -> 1323 (e.getPaSyncState() 1324 != BluetoothLeBroadcastReceiveState 1325 .PA_SYNC_STATE_SYNCHRONIZED)) 1326 .findAny(); 1327 if (receiver.isPresent()) { 1328 sourceId = receiver.get().getSourceId(); 1329 } else { 1330 // If all sources are synced, continue to pick the 1st source 1331 sourceId = sources.get(0).getSourceId(); 1332 } 1333 return sourceId; 1334 } 1335 1336 private BassClientStateMachine getOrCreateStateMachine(BluetoothDevice device) { 1337 if (device == null) { 1338 Log.e(TAG, "getOrCreateStateMachine failed: device cannot be null"); 1339 return null; 1340 } 1341 synchronized (mStateMachines) { 1342 BassClientStateMachine stateMachine = mStateMachines.get(device); 1343 if (stateMachine != null) { 1344 return stateMachine; 1345 } 1346 1347 log("Creating a new state machine for " + device); 1348 stateMachine = 1349 BassObjectsFactory.getInstance() 1350 .makeStateMachine( 1351 device, 1352 this, 1353 mAdapterService, 1354 mStateMachinesThread.getLooper()); 1355 if (stateMachine != null) { 1356 mStateMachines.put(device, stateMachine); 1357 } 1358 1359 return stateMachine; 1360 } 1361 } 1362 1363 class DialingOutTimeoutEvent implements Runnable { 1364 Integer mBroadcastId; 1365 1366 DialingOutTimeoutEvent(Integer broadcastId) { 1367 mBroadcastId = broadcastId; 1368 } 1369 1370 @Override 1371 public void run() { 1372 mDialingOutTimeoutEvent = null; 1373 1374 if (getBassClientService() == null) { 1375 Log.e(TAG, "DialingOutTimeoutEvent: No Bass service"); 1376 return; 1377 } 1378 1379 LeAudioService leAudioService = mServiceFactory.getLeAudioService(); 1380 if (leAudioService == null) { 1381 Log.d(TAG, "DialingOutTimeoutEvent: No available LeAudioService"); 1382 return; 1383 } 1384 1385 sEventLogger.logd(TAG, "Broadcast timeout: " + mBroadcastId); 1386 mLocalBroadcastReceivers.remove(mBroadcastId); 1387 leAudioService.stopBroadcast(mBroadcastId); 1388 } 1389 1390 public boolean isScheduledForBroadcast(Integer broadcastId) { 1391 return mBroadcastId.equals(broadcastId); 1392 } 1393 } 1394 1395 /** 1396 * Get the BassClientService instance 1397 * 1398 * @return BassClientService instance 1399 */ 1400 public static synchronized BassClientService getBassClientService() { 1401 if (sService == null) { 1402 Log.w(TAG, "getBassClientService(): service is NULL"); 1403 return null; 1404 } 1405 if (!sService.isAvailable()) { 1406 Log.w(TAG, "getBassClientService(): service is not available"); 1407 return null; 1408 } 1409 return sService; 1410 } 1411 1412 private void removeStateMachine(BluetoothDevice device) { 1413 synchronized (mStateMachines) { 1414 BassClientStateMachine sm = mStateMachines.get(device); 1415 if (sm == null) { 1416 Log.w( 1417 TAG, 1418 "removeStateMachine: device " + device + " does not have a state machine"); 1419 return; 1420 } 1421 log("removeStateMachine: removing state machine for device: " + device); 1422 sm.doQuit(); 1423 sm.cleanup(); 1424 mStateMachines.remove(device); 1425 } 1426 1427 // Cleanup device cache 1428 mPendingGroupOp.remove(device); 1429 mGroupManagedSources.remove(device); 1430 mActiveSourceMap.remove(device); 1431 } 1432 1433 private void handleReconnectingAudioSharingModeDevice(BluetoothDevice device) { 1434 /* In case of reconnecting Audio Sharing mode device */ 1435 if (mDialingOutTimeoutEvent != null) { 1436 for (Map.Entry<Integer, HashSet<BluetoothDevice>> entry : 1437 mLocalBroadcastReceivers.entrySet()) { 1438 Integer broadcastId = entry.getKey(); 1439 HashSet<BluetoothDevice> devices = entry.getValue(); 1440 1441 /* If associated with any broadcast, try to remove pending timeout callback */ 1442 if ((mDialingOutTimeoutEvent.isScheduledForBroadcast(broadcastId)) 1443 && (devices.contains(device))) { 1444 Log.i( 1445 TAG, 1446 "connectionStateChanged: reconnected previously synced device: " 1447 + device); 1448 mHandler.removeCallbacks(mDialingOutTimeoutEvent); 1449 mDialingOutTimeoutEvent = null; 1450 break; 1451 } 1452 } 1453 } 1454 } 1455 1456 private void informConnectedDeviceAboutScanOffloadStop() { 1457 for (BluetoothDevice device : getConnectedDevices()) { 1458 synchronized (mStateMachines) { 1459 BassClientStateMachine stateMachine = getOrCreateStateMachine(device); 1460 if (stateMachine == null) { 1461 Log.w( 1462 TAG, 1463 "informConnectedDeviceAboutScanOffloadStop: Can't get state " 1464 + "machine for device: " 1465 + device); 1466 continue; 1467 } 1468 stateMachine.sendMessage(BassClientStateMachine.STOP_SCAN_OFFLOAD); 1469 } 1470 } 1471 } 1472 1473 private int validateParametersForSourceOperation( 1474 BassClientStateMachine stateMachine, BluetoothDevice device) { 1475 if (stateMachine == null) { 1476 log("validateParameters: stateMachine is null for device: " + device); 1477 return BluetoothStatusCodes.ERROR_BAD_PARAMETERS; 1478 } 1479 1480 if (getConnectionState(device) != STATE_CONNECTED) { 1481 log("validateParameters: device is not connected, device: " + device); 1482 return BluetoothStatusCodes.ERROR_REMOTE_LINK_ERROR; 1483 } 1484 1485 return BluetoothStatusCodes.SUCCESS; 1486 } 1487 1488 private int validateParametersForSourceOperation( 1489 BassClientStateMachine stateMachine, 1490 BluetoothDevice device, 1491 BluetoothLeBroadcastMetadata metadata) { 1492 int status = validateParametersForSourceOperation(stateMachine, device); 1493 if (status != BluetoothStatusCodes.SUCCESS) { 1494 return status; 1495 } 1496 1497 if (metadata == null) { 1498 log("validateParameters: metadata is null for device: " + device); 1499 return BluetoothStatusCodes.ERROR_BAD_PARAMETERS; 1500 } 1501 1502 byte[] code = metadata.getBroadcastCode(); 1503 if ((code != null) && (code.length != 0)) { 1504 if ((code.length > 16) || (code.length < 4)) { 1505 log( 1506 "validateParameters: Invalid broadcast code length: " 1507 + code.length 1508 + ", should be between 4 and 16 octets"); 1509 return BluetoothStatusCodes.ERROR_BAD_PARAMETERS; 1510 } 1511 } 1512 1513 return BluetoothStatusCodes.SUCCESS; 1514 } 1515 1516 private int validateParametersForSourceOperation( 1517 BassClientStateMachine stateMachine, BluetoothDevice device, Integer sourceId) { 1518 int status = validateParametersForSourceOperation(stateMachine, device); 1519 if (status != BluetoothStatusCodes.SUCCESS) { 1520 return status; 1521 } 1522 1523 if (sourceId == BassConstants.INVALID_SOURCE_ID) { 1524 log("validateParameters: no such sourceId for device: " + device); 1525 return BluetoothStatusCodes.ERROR_LE_BROADCAST_ASSISTANT_INVALID_SOURCE_ID; 1526 } 1527 1528 return BluetoothStatusCodes.SUCCESS; 1529 } 1530 1531 private int validateParametersForSourceOperation( 1532 BassClientStateMachine stateMachine, 1533 BluetoothDevice device, 1534 BluetoothLeBroadcastMetadata metadata, 1535 Integer sourceId) { 1536 int status = validateParametersForSourceOperation(stateMachine, device, metadata); 1537 if (status != BluetoothStatusCodes.SUCCESS) { 1538 return status; 1539 } 1540 1541 if (sourceId == BassConstants.INVALID_SOURCE_ID) { 1542 log("validateParameters: no such sourceId for device: " + device); 1543 return BluetoothStatusCodes.ERROR_LE_BROADCAST_ASSISTANT_INVALID_SOURCE_ID; 1544 } 1545 1546 return BluetoothStatusCodes.SUCCESS; 1547 } 1548 1549 void handleConnectionStateChanged(BluetoothDevice device, int fromState, int toState) { 1550 mHandler.post(() -> connectionStateChanged(device, fromState, toState)); 1551 } 1552 1553 synchronized void connectionStateChanged(BluetoothDevice device, int fromState, int toState) { 1554 if (!isAvailable()) { 1555 Log.w(TAG, "connectionStateChanged: service is not available"); 1556 return; 1557 } 1558 1559 if ((device == null) || (fromState == toState)) { 1560 Log.e( 1561 TAG, 1562 "connectionStateChanged: unexpected invocation. device=" 1563 + device 1564 + " fromState=" 1565 + fromState 1566 + " toState=" 1567 + toState); 1568 return; 1569 } 1570 1571 sEventLogger.logd( 1572 TAG, 1573 "connectionStateChanged: device: " 1574 + device 1575 + ", fromState= " 1576 + BluetoothProfile.getConnectionStateName(fromState) 1577 + ", toState= " 1578 + BluetoothProfile.getConnectionStateName(toState)); 1579 1580 // Check if the device is disconnected - if unbond, remove the state machine 1581 if (toState == STATE_DISCONNECTED) { 1582 mPendingGroupOp.remove(device); 1583 mPausedBroadcastSinks.remove(device); 1584 synchronized (mSinksWaitingForPast) { 1585 mSinksWaitingForPast.remove(device); 1586 } 1587 synchronized (mPendingSourcesToAdd) { 1588 mPendingSourcesToAdd.removeIf( 1589 pendingSourcesToAdd -> pendingSourcesToAdd.sink.equals(device)); 1590 } 1591 1592 int bondState = mAdapterService.getBondState(device); 1593 if (bondState == BluetoothDevice.BOND_NONE) { 1594 log("Unbonded " + device + ". Removing state machine"); 1595 removeStateMachine(device); 1596 } 1597 1598 checkAndStopBigMonitoring(); 1599 removeSinkMetadataFromGroupIfWholeUnsynced(device); 1600 1601 if (getConnectedDevices().isEmpty() 1602 || (mPausedBroadcastSinks.isEmpty() 1603 && mSinksWaitingForPast.isEmpty() 1604 && mPendingSourcesToAdd.isEmpty() 1605 && !isAnyConnectedDeviceSwitchingSource())) { 1606 synchronized (mSearchScanCallbackLock) { 1607 // when searching is stopped then clear all sync data 1608 if (!isSearchInProgress()) { 1609 clearAllSyncData(); 1610 } 1611 } 1612 } 1613 1614 /* Restore allowed context mask for unicast in case if last connected broadcast 1615 * delegator device which has external source disconnects. 1616 */ 1617 checkAndResetGroupAllowedContextMask(); 1618 } else if (toState == STATE_CONNECTED) { 1619 handleReconnectingAudioSharingModeDevice(device); 1620 } 1621 } 1622 1623 public void handleBondStateChanged(BluetoothDevice device, int fromState, int toState) { 1624 mHandler.post(() -> bondStateChanged(device, toState)); 1625 } 1626 1627 @VisibleForTesting 1628 void bondStateChanged(BluetoothDevice device, int bondState) { 1629 log("Bond state changed for device: " + device + " state: " + bondState); 1630 1631 // Remove state machine if the bonding for a device is removed 1632 if (bondState != BluetoothDevice.BOND_NONE) { 1633 return; 1634 } 1635 1636 synchronized (mStateMachines) { 1637 BassClientStateMachine sm = mStateMachines.get(device); 1638 if (sm == null) { 1639 return; 1640 } 1641 if (sm.getConnectionState() != STATE_DISCONNECTED) { 1642 Log.i(TAG, "Disconnecting device because it was unbonded."); 1643 disconnect(device); 1644 return; 1645 } 1646 removeStateMachine(device); 1647 } 1648 } 1649 1650 /** 1651 * Connects the bass profile to the passed in device 1652 * 1653 * @param device is the device with which we will connect the Bass profile 1654 * @return true if BAss profile successfully connected, false otherwise 1655 */ 1656 public boolean connect(BluetoothDevice device) { 1657 Log.d(TAG, "connect(): " + device); 1658 if (device == null) { 1659 Log.e(TAG, "connect: device is null"); 1660 return false; 1661 } 1662 if (getConnectionPolicy(device) == CONNECTION_POLICY_FORBIDDEN) { 1663 Log.e(TAG, "connect: connection policy set to forbidden"); 1664 return false; 1665 } 1666 synchronized (mStateMachines) { 1667 BassClientStateMachine stateMachine = getOrCreateStateMachine(device); 1668 if (stateMachine == null) { 1669 Log.e(TAG, "Can't get state machine for device: " + device); 1670 return false; 1671 } 1672 1673 stateMachine.sendMessage(BassClientStateMachine.CONNECT); 1674 } 1675 return true; 1676 } 1677 1678 /** 1679 * Disconnects BassClient profile for the passed in device 1680 * 1681 * @param device is the device with which we want to disconnected the BAss client profile 1682 * @return true if Bass client profile successfully disconnected, false otherwise 1683 */ 1684 public boolean disconnect(BluetoothDevice device) { 1685 Log.d(TAG, "disconnect(): " + device); 1686 if (device == null) { 1687 Log.e(TAG, "disconnect: device is null"); 1688 return false; 1689 } 1690 synchronized (mStateMachines) { 1691 BassClientStateMachine stateMachine = getOrCreateStateMachine(device); 1692 if (stateMachine == null) { 1693 Log.e(TAG, "Can't get state machine for device: " + device); 1694 return false; 1695 } 1696 1697 stateMachine.sendMessage(BassClientStateMachine.DISCONNECT); 1698 } 1699 return true; 1700 } 1701 1702 /** 1703 * Check whether can connect to a peer device. The check considers a number of factors during 1704 * the evaluation. 1705 * 1706 * @param device the peer device to connect to 1707 * @return true if connection is allowed, otherwise false 1708 */ 1709 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) 1710 public boolean okToConnect(BluetoothDevice device) { 1711 // Check if this is an incoming connection in Quiet mode. 1712 if (mAdapterService.isQuietModeEnabled()) { 1713 Log.e(TAG, "okToConnect: cannot connect to " + device + " : quiet mode enabled"); 1714 return false; 1715 } 1716 // Check connection policy and accept or reject the connection. 1717 int connectionPolicy = getConnectionPolicy(device); 1718 int bondState = mAdapterService.getBondState(device); 1719 // Allow this connection only if the device is bonded. Any attempt to connect while 1720 // bonding would potentially lead to an unauthorized connection. 1721 if (bondState != BluetoothDevice.BOND_BONDED) { 1722 Log.w(TAG, "okToConnect: return false, bondState=" + bondState); 1723 return false; 1724 } else if (connectionPolicy != CONNECTION_POLICY_UNKNOWN 1725 && connectionPolicy != CONNECTION_POLICY_ALLOWED) { 1726 // Otherwise, reject the connection if connectionPolicy is not valid. 1727 Log.w(TAG, "okToConnect: return false, connectionPolicy=" + connectionPolicy); 1728 return false; 1729 } 1730 return true; 1731 } 1732 1733 /** 1734 * Get connection state of remote device 1735 * 1736 * @param sink the remote device 1737 * @return connection state 1738 */ 1739 public int getConnectionState(BluetoothDevice sink) { 1740 synchronized (mStateMachines) { 1741 BassClientStateMachine sm = getOrCreateStateMachine(sink); 1742 if (sm == null) { 1743 log("getConnectionState returns STATE_DISC"); 1744 return STATE_DISCONNECTED; 1745 } 1746 return sm.getConnectionState(); 1747 } 1748 } 1749 1750 /** 1751 * Get a list of all LE Audio Broadcast Sinks with the specified connection states. 1752 * 1753 * @param states states array representing the connection states 1754 * @return a list of devices that match the provided connection states 1755 */ 1756 List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 1757 ArrayList<BluetoothDevice> devices = new ArrayList<>(); 1758 if (states == null) { 1759 return devices; 1760 } 1761 final BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices(); 1762 if (bondedDevices == null) { 1763 return devices; 1764 } 1765 synchronized (mStateMachines) { 1766 for (BluetoothDevice device : bondedDevices) { 1767 final ParcelUuid[] featureUuids = mAdapterService.getRemoteUuids(device); 1768 if (!Utils.arrayContains(featureUuids, BluetoothUuid.BASS)) { 1769 continue; 1770 } 1771 int connectionState = STATE_DISCONNECTED; 1772 BassClientStateMachine sm = getOrCreateStateMachine(device); 1773 if (sm != null) { 1774 connectionState = sm.getConnectionState(); 1775 } 1776 for (int state : states) { 1777 if (connectionState == state) { 1778 devices.add(device); 1779 break; 1780 } 1781 } 1782 } 1783 return devices; 1784 } 1785 } 1786 1787 /** 1788 * Get a list of all LE Audio Broadcast Sinks connected with the LE Audio Broadcast Assistant. 1789 * 1790 * @return list of connected devices 1791 */ 1792 public List<BluetoothDevice> getConnectedDevices() { 1793 synchronized (mStateMachines) { 1794 List<BluetoothDevice> devices = new ArrayList<>(); 1795 for (BassClientStateMachine sm : mStateMachines.values()) { 1796 if (sm.isConnected()) { 1797 devices.add(sm.getDevice()); 1798 } 1799 } 1800 return devices; 1801 } 1802 } 1803 1804 /** 1805 * Set the connectionPolicy of the Broadcast Audio Scan Service profile. 1806 * 1807 * <p>The connection policy can be one of: {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED}, 1808 * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}, {@link 1809 * BluetoothProfile#CONNECTION_POLICY_UNKNOWN} 1810 * 1811 * @param device paired bluetooth device 1812 * @param connectionPolicy is the connection policy to set to for this profile 1813 * @return true if connectionPolicy is set, false on error 1814 */ 1815 public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) { 1816 Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy); 1817 boolean setSuccessfully = 1818 mDatabaseManager.setProfileConnectionPolicy( 1819 device, BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT, connectionPolicy); 1820 if (setSuccessfully && connectionPolicy == CONNECTION_POLICY_ALLOWED) { 1821 connect(device); 1822 } else if (setSuccessfully && connectionPolicy == CONNECTION_POLICY_FORBIDDEN) { 1823 disconnect(device); 1824 } 1825 return setSuccessfully; 1826 } 1827 1828 /** 1829 * Get the connection policy of the profile. 1830 * 1831 * <p>The connection policy can be any of: {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED}, 1832 * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}, {@link 1833 * BluetoothProfile#CONNECTION_POLICY_UNKNOWN} 1834 * 1835 * @param device paired bluetooth device 1836 * @return connection policy of the device 1837 */ 1838 public int getConnectionPolicy(BluetoothDevice device) { 1839 return mDatabaseManager.getProfileConnectionPolicy( 1840 device, BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT); 1841 } 1842 1843 /** 1844 * Register callbacks that will be invoked during scan offloading. 1845 * 1846 * @param cb callbacks to be invoked 1847 */ 1848 public void registerCallback(IBluetoothLeBroadcastAssistantCallback cb) { 1849 Log.i(TAG, "registerCallback"); 1850 mCallbacks.register(cb); 1851 } 1852 1853 /** 1854 * Unregister callbacks that are invoked during scan offloading. 1855 * 1856 * @param cb callbacks to be unregistered 1857 */ 1858 public void unregisterCallback(IBluetoothLeBroadcastAssistantCallback cb) { 1859 Log.i(TAG, "unregisterCallback"); 1860 mCallbacks.unregister(cb); 1861 } 1862 1863 /** 1864 * Search for LE Audio Broadcast Sources on behalf of all devices connected via Broadcast Audio 1865 * Scan Service, filtered by filters 1866 * 1867 * @param filters ScanFilters for finding exact Broadcast Source 1868 */ 1869 @SuppressLint("AndroidFrameworkRequiresPermission") // TODO: b/350563786 - Fix BASS annotation 1870 public void startSearchingForSources(List<ScanFilter> filters) { 1871 log("startSearchingForSources"); 1872 1873 if (!BluetoothMethodProxy.getInstance() 1874 .initializePeriodicAdvertisingManagerOnDefaultAdapter()) { 1875 Log.e(TAG, "Failed to initialize Periodic Advertising Manager on Default Adapter"); 1876 mCallbacks.notifySearchStartFailed(BluetoothStatusCodes.ERROR_UNKNOWN); 1877 return; 1878 } 1879 1880 synchronized (mSearchScanCallbackLock) { 1881 if (!leaudioBassScanWithInternalScanController()) { 1882 if (mBluetoothLeScannerWrapper == null) { 1883 mBluetoothLeScannerWrapper = 1884 BassObjectsFactory.getInstance() 1885 .getBluetoothLeScannerWrapper(mBluetoothAdapter); 1886 } 1887 if (mBluetoothLeScannerWrapper == null) { 1888 Log.e(TAG, "startLeScan: cannot get BluetoothLeScanner"); 1889 mCallbacks.notifySearchStartFailed(BluetoothStatusCodes.ERROR_UNKNOWN); 1890 return; 1891 } 1892 } 1893 if (isSearchInProgress()) { 1894 Log.e(TAG, "LE Scan has already started"); 1895 mCallbacks.notifySearchStartFailed( 1896 BluetoothStatusCodes.ERROR_ALREADY_IN_TARGET_STATE); 1897 return; 1898 } 1899 if (!leaudioBassScanWithInternalScanController()) { 1900 mSearchScanCallback = 1901 new ScanCallback() { 1902 @Override 1903 public void onScanResult(int callbackType, ScanResult result) { 1904 log("onScanResult:" + result); 1905 synchronized (mSearchScanCallbackLock) { 1906 // check mSearchScanCallback because even after 1907 // mBluetoothLeScannerWrapper.stopScan(mSearchScanCallback) that 1908 // callback could be called 1909 if (mSearchScanCallback == null) { 1910 log("onScanResult: scanner already stopped"); 1911 return; 1912 } 1913 if (callbackType != ScanSettings.CALLBACK_TYPE_ALL_MATCHES) { 1914 // Should not happen 1915 Log.e(TAG, "LE Scan has already started"); 1916 return; 1917 } 1918 Integer broadcastId = BassUtils.getBroadcastId(result); 1919 if (broadcastId == BassConstants.INVALID_BROADCAST_ID) { 1920 Log.d(TAG, "onScanResult: Broadcast ID is invalid"); 1921 return; 1922 } 1923 1924 log("Broadcast Source Found:" + result.getDevice()); 1925 sEventLogger.logd( 1926 TAG, 1927 "Broadcast Source Found: Broadcast ID: " + broadcastId); 1928 1929 if (!mCachedBroadcasts.containsKey(broadcastId)) { 1930 log("selectBroadcastSource: broadcastId " + broadcastId); 1931 mCachedBroadcasts.put(broadcastId, result); 1932 addSelectSourceRequest( 1933 broadcastId, /* hasPriority */ false); 1934 } else { 1935 if (leaudioBroadcastResyncHelper() 1936 && mTimeoutHandler.isStarted( 1937 broadcastId, MESSAGE_SYNC_LOST_TIMEOUT)) { 1938 mTimeoutHandler.stop( 1939 broadcastId, MESSAGE_SYNC_LOST_TIMEOUT); 1940 mTimeoutHandler.start( 1941 broadcastId, 1942 MESSAGE_SYNC_LOST_TIMEOUT, 1943 sSyncLostTimeout); 1944 } 1945 if (isSinkUnintentionalPauseType(broadcastId)) { 1946 addSelectSourceRequest( 1947 broadcastId, /* hasPriority= */ true); 1948 } 1949 } 1950 } 1951 } 1952 1953 public void onScanFailed(int errorCode) { 1954 Log.e(TAG, "Scan Failure:" + errorCode); 1955 informConnectedDeviceAboutScanOffloadStop(); 1956 } 1957 }; 1958 } 1959 mSyncFailureCounter.clear(); 1960 mHandler.removeMessages(MESSAGE_SYNC_TIMEOUT); 1961 if (leaudioBroadcastResyncHelper()) { 1962 if (leaudioBroadcastPreventResumeInterruption()) { 1963 // Collect broadcasts which should be sync and/or cache should remain. 1964 // Broadcasts, which has to be synced, needs to have cache available. 1965 // Broadcasts which only cache should remain (i.e. because of potential resume) 1966 // has to be synced too to show it on the list before resume. 1967 LinkedHashSet<Integer> broadcastsToSync = new LinkedHashSet<>(); 1968 1969 // Keep already synced broadcasts 1970 broadcastsToSync.addAll(getBroadcastIdsOfSyncedBroadcasters()); 1971 1972 // Sync to the broadcasts already synced with sinks 1973 broadcastsToSync.addAll(getExternalBroadcastsActiveOnSinks()); 1974 1975 // Sync to the broadcasts waiting for PAST 1976 broadcastsToSync.addAll(getBroadcastIdsWaitingForPAST()); 1977 1978 // Sync to the broadcasts waiting for adding source (could be by resume too). 1979 broadcastsToSync.addAll(getBroadcastIdsWaitingForAddSource()); 1980 1981 // Sync to the paused broadcasts (INTENTIONAL and UNINTENTIONAL) based on the 1982 // mPausedBroadcastSinks as mPausedBroadcastIds could be already removed by 1983 // resume execution 1984 broadcastsToSync.addAll(getPausedBroadcastIdsBasedOnSinks()); 1985 1986 log("Broadcasts to sync on start: " + broadcastsToSync); 1987 1988 // Add broadcasts to sync queue 1989 for (int broadcastId : broadcastsToSync) { 1990 addSelectSourceRequest(broadcastId, /* hasPriority */ true); 1991 } 1992 1993 // When starting scan, clear the previously cached broadcast scan results, 1994 // skip broadcast already added to sync 1995 mCachedBroadcasts.keySet().removeIf(key -> !broadcastsToSync.contains(key)); 1996 1997 printAllSyncData(); 1998 } else { 1999 // Sync to the broadcasts already synced with sinks 2000 Set<Integer> syncedBroadcasts = getExternalBroadcastsActiveOnSinks(); 2001 for (int syncedBroadcast : syncedBroadcasts) { 2002 addSelectSourceRequest(syncedBroadcast, /* hasPriority */ true); 2003 } 2004 // when starting scan, clear the previously cached broadcast scan results 2005 mCachedBroadcasts 2006 .keySet() 2007 .removeIf( 2008 key -> 2009 !mPausedBroadcastIds.containsKey(key) 2010 || !mPausedBroadcastIds 2011 .get(key) 2012 .equals(PauseType.SINK_UNINTENTIONAL)); 2013 } 2014 } else { 2015 // When starting scan, clear the previously cached broadcast scan results 2016 mCachedBroadcasts.clear(); 2017 } 2018 // Clear previous sources notify flag before scanning new result 2019 // this is to make sure the active sources are notified even if already synced 2020 clearNotifiedFlags(); 2021 2022 for (BluetoothDevice device : getConnectedDevices()) { 2023 synchronized (mStateMachines) { 2024 BassClientStateMachine stateMachine = getOrCreateStateMachine(device); 2025 if (stateMachine == null) { 2026 Log.w( 2027 TAG, 2028 "startSearchingForSources: Can't get state machine for " 2029 + "device: " 2030 + device); 2031 continue; 2032 } 2033 stateMachine.sendMessage(BassClientStateMachine.START_SCAN_OFFLOAD); 2034 } 2035 } 2036 2037 if (leaudioBassScanWithInternalScanController()) { 2038 mBassScanCallback.registerAndStartScan(filters); 2039 // Invoke search callbacks in onScannerRegistered 2040 } else { 2041 ScanSettings settings = 2042 new ScanSettings.Builder() 2043 .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES) 2044 .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) 2045 .setLegacy(false) 2046 .build(); 2047 if (filters == null) { 2048 filters = new ArrayList<ScanFilter>(); 2049 } 2050 if (!BassUtils.containUuid(filters, BassConstants.BAAS_UUID)) { 2051 byte[] serviceData = {0x00, 0x00, 0x00}; // Broadcast_ID 2052 byte[] serviceDataMask = {0x00, 0x00, 0x00}; 2053 2054 filters.add( 2055 new ScanFilter.Builder() 2056 .setServiceData( 2057 BassConstants.BAAS_UUID, serviceData, serviceDataMask) 2058 .build()); 2059 } 2060 mBluetoothLeScannerWrapper.startScan(filters, settings, mSearchScanCallback); 2061 mCallbacks.notifySearchStarted(BluetoothStatusCodes.REASON_LOCAL_APP_REQUEST); 2062 } 2063 sEventLogger.logd(TAG, "startSearchingForSources"); 2064 } 2065 } 2066 2067 /** Stops an ongoing search for nearby Broadcast Sources */ 2068 public void stopSearchingForSources() { 2069 log("stopSearchingForSources"); 2070 synchronized (mSearchScanCallbackLock) { 2071 if (leaudioBassScanWithInternalScanController()) { 2072 if (!isSearchInProgress()) { 2073 Log.e(TAG, "stopSearchingForSources: Scan not started yet"); 2074 mCallbacks.notifySearchStopFailed( 2075 BluetoothStatusCodes.ERROR_ALREADY_IN_TARGET_STATE); 2076 return; 2077 } 2078 mBassScanCallback.stopScanAndUnregister(); 2079 } else { 2080 if (mBluetoothLeScannerWrapper == null || mSearchScanCallback == null) { 2081 Log.e(TAG, "stopSearchingForSources: Scan not started yet"); 2082 mCallbacks.notifySearchStopFailed( 2083 BluetoothStatusCodes.ERROR_ALREADY_IN_TARGET_STATE); 2084 return; 2085 } 2086 mBluetoothLeScannerWrapper.stopScan(mSearchScanCallback); 2087 mBluetoothLeScannerWrapper = null; 2088 mSearchScanCallback = null; 2089 } 2090 2091 if (leaudioBroadcastPreventResumeInterruption()) { 2092 printAllSyncData(); 2093 2094 // Collect broadcasts which should stay synced after search stops 2095 HashSet<Integer> broadcastsToKeepSynced = new HashSet<>(); 2096 2097 // Keep broadcasts waiting for PAST 2098 broadcastsToKeepSynced.addAll(getBroadcastIdsWaitingForPAST()); 2099 2100 // Keep broadcasts waiting for adding source (could be by resume too) 2101 broadcastsToKeepSynced.addAll(getBroadcastIdsWaitingForAddSource()); 2102 2103 // Keep broadcast unintentionally paused 2104 broadcastsToKeepSynced.addAll(getUnintentionallyPausedBroadcastIds()); 2105 2106 log("Broadcasts to keep on stop: " + broadcastsToKeepSynced); 2107 2108 // Remove all other broadcasts from sync queue if not in broadcastsToKeepSynced 2109 synchronized (mSourceSyncRequestsQueue) { 2110 Iterator<SourceSyncRequest> iterator = mSourceSyncRequestsQueue.iterator(); 2111 while (iterator.hasNext()) { 2112 SourceSyncRequest sourceSyncRequest = iterator.next(); 2113 Integer queuedBroadcastId = 2114 BassUtils.getBroadcastId(sourceSyncRequest.scanResult); 2115 if (!broadcastsToKeepSynced.contains(queuedBroadcastId)) { 2116 iterator.remove(); 2117 } 2118 } 2119 } 2120 2121 // Collect broadcasts (sync handles) which should be unsynced (not in keep list) 2122 List<Integer> syncHandlesToRemove = 2123 new ArrayList<>(mSyncHandleToBroadcastIdMap.keySet()); 2124 for (int broadcastId : broadcastsToKeepSynced) { 2125 syncHandlesToRemove.remove(getSyncHandleForBroadcastId(broadcastId)); 2126 // Add again, as unintentionally paused broadcasts were monitored in 2127 // onScanResult during scanning, now need to be monitored in the sync loop 2128 addSelectSourceRequest(broadcastId, /* hasPriority */ true); 2129 } 2130 2131 // Unsync not needed broadcasts 2132 for (int syncHandleToRemove : syncHandlesToRemove) { 2133 cancelActiveSync(syncHandleToRemove); 2134 } 2135 2136 mSyncFailureCounter.clear(); 2137 2138 printAllSyncData(); 2139 } else { 2140 clearAllSyncData(); 2141 } 2142 2143 informConnectedDeviceAboutScanOffloadStop(); 2144 sEventLogger.logd(TAG, "stopSearchingForSources"); 2145 mCallbacks.notifySearchStopped(BluetoothStatusCodes.REASON_LOCAL_APP_REQUEST); 2146 2147 if (!leaudioBroadcastPreventResumeInterruption()) { 2148 for (Map.Entry<Integer, PauseType> entry : mPausedBroadcastIds.entrySet()) { 2149 Integer broadcastId = entry.getKey(); 2150 PauseType pauseType = entry.getValue(); 2151 if (pauseType != PauseType.HOST_INTENTIONAL) { 2152 addSelectSourceRequest(broadcastId, /* hasPriority */ true); 2153 } 2154 } 2155 } 2156 } 2157 } 2158 2159 private void printAllSyncData() { 2160 Log.v( 2161 TAG, 2162 "printAllSyncData" 2163 + ("\n mActiveSyncedSources: " + mActiveSyncedSources) 2164 + ("\n mPeriodicAdvCallbacksMap: " + mPeriodicAdvCallbacksMap) 2165 + ("\n mSyncHandleToBaseDataMap: " + mSyncHandleToBaseDataMap) 2166 + ("\n mBisDiscoveryCounterMap: " + mBisDiscoveryCounterMap) 2167 + ("\n mSyncHandleToDeviceMap: " + mSyncHandleToDeviceMap) 2168 + ("\n mSyncHandleToBroadcastIdMap: " + mSyncHandleToBroadcastIdMap) 2169 + ("\n mPeriodicAdvertisementResultMap: " + mPeriodicAdvertisementResultMap) 2170 + ("\n mSourceSyncRequestsQueue: " + mSourceSyncRequestsQueue) 2171 + ("\n mSyncFailureCounter: " + mSyncFailureCounter) 2172 + ("\n mPendingSourcesToAdd: " + mPendingSourcesToAdd) 2173 + ("\n mSinksWaitingForPast: " + mSinksWaitingForPast) 2174 + ("\n mPausedBroadcastIds: " + mPausedBroadcastIds) 2175 + ("\n mPausedBroadcastSinks: " + mPausedBroadcastSinks) 2176 + ("\n mCachedBroadcasts: " + mCachedBroadcasts) 2177 + ("\n mBroadcastMetadataMap: " + mBroadcastMetadataMap)); 2178 } 2179 2180 private void clearAllSyncData() { 2181 log("clearAllSyncData"); 2182 synchronized (mSourceSyncRequestsQueue) { 2183 mTimeoutHandler.stopAll(MESSAGE_SYNC_LOST_TIMEOUT); 2184 mSourceSyncRequestsQueue.clear(); 2185 mSyncFailureCounter.clear(); 2186 if (!leaudioBroadcastPreventResumeInterruption()) { 2187 mPendingSourcesToAdd.clear(); 2188 } 2189 2190 cancelActiveSync(null); 2191 mActiveSyncedSources.clear(); 2192 mPeriodicAdvCallbacksMap.clear(); 2193 mBisDiscoveryCounterMap.clear(); 2194 2195 mSyncHandleToDeviceMap.clear(); 2196 mSyncHandleToBaseDataMap.clear(); 2197 mSyncHandleToBroadcastIdMap.clear(); 2198 mPeriodicAdvertisementResultMap.clear(); 2199 } 2200 } 2201 2202 /** 2203 * Return true if a search has been started by this application 2204 * 2205 * @return true if a search has been started by this application 2206 */ 2207 public boolean isSearchInProgress() { 2208 synchronized (mSearchScanCallbackLock) { 2209 if (leaudioBassScanWithInternalScanController()) { 2210 return mBassScanCallback.isBroadcastAudioAnnouncementScanActive(); 2211 } else { 2212 return mSearchScanCallback != null; 2213 } 2214 } 2215 } 2216 2217 /** Internal periodic Advertising manager callback */ 2218 final class PACallback extends PeriodicAdvertisingCallback { 2219 @Override 2220 public void onSyncEstablished( 2221 int syncHandle, 2222 BluetoothDevice device, 2223 int advertisingSid, 2224 int skip, 2225 int timeout, 2226 int status) { 2227 int broadcastId = getBroadcastIdForSyncHandle(BassConstants.PENDING_SYNC_HANDLE); 2228 log( 2229 "onSyncEstablished syncHandle: " 2230 + syncHandle 2231 + ", broadcastId: " 2232 + broadcastId 2233 + ", device: " 2234 + device 2235 + ", advertisingSid: " 2236 + advertisingSid 2237 + ", skip: " 2238 + skip 2239 + ", timeout: " 2240 + timeout 2241 + ", status: " 2242 + status); 2243 2244 if (broadcastId == BassConstants.INVALID_BROADCAST_ID) { 2245 Log.w(TAG, "onSyncEstablished unexpected call, no pending synchronization"); 2246 handleSelectSourceRequest(); 2247 return; 2248 } 2249 2250 final int ERROR_CODE_SUCCESS = 0x00; 2251 if (status != ERROR_CODE_SUCCESS) { 2252 log("onSyncEstablished failed for broadcast id: " + broadcastId); 2253 boolean notifiedOfLost = false; 2254 synchronized (mPendingSourcesToAdd) { 2255 Iterator<AddSourceData> iterator = mPendingSourcesToAdd.iterator(); 2256 while (iterator.hasNext()) { 2257 AddSourceData pendingSourcesToAdd = iterator.next(); 2258 if (pendingSourcesToAdd.sourceMetadata.getBroadcastId() == broadcastId) { 2259 if (!notifiedOfLost) { 2260 notifiedOfLost = true; 2261 mCallbacks.notifySourceLost(broadcastId); 2262 } 2263 mCallbacks.notifySourceAddFailed( 2264 pendingSourcesToAdd.sink, 2265 pendingSourcesToAdd.sourceMetadata, 2266 BluetoothStatusCodes.ERROR_LOCAL_NOT_ENOUGH_RESOURCES); 2267 iterator.remove(); 2268 } 2269 } 2270 } 2271 synchronized (mSourceSyncRequestsQueue) { 2272 int failsCounter = mSyncFailureCounter.getOrDefault(broadcastId, 0) + 1; 2273 mSyncFailureCounter.put(broadcastId, failsCounter); 2274 } 2275 2276 // It has to be cleared before calling addSelectSourceRequest to properly add it as 2277 // it is a duplicate 2278 clearAllDataForSyncHandle(BassConstants.PENDING_SYNC_HANDLE); 2279 2280 if (isSinkUnintentionalPauseType(broadcastId)) { 2281 if (!mTimeoutHandler.isStarted( 2282 broadcastId, MESSAGE_BROADCAST_MONITOR_TIMEOUT)) { 2283 mTimeoutHandler.start( 2284 broadcastId, 2285 MESSAGE_BROADCAST_MONITOR_TIMEOUT, 2286 sBroadcasterMonitorTimeout); 2287 } 2288 if (!isSearchInProgress()) { 2289 addSelectSourceRequest(broadcastId, /* hasPriority */ true); 2290 } 2291 } else { 2292 // Clear from cache to make possible sync again (only during active searching) 2293 synchronized (mSearchScanCallbackLock) { 2294 if (isSearchInProgress()) { 2295 mCachedBroadcasts.remove(broadcastId); 2296 } 2297 } 2298 } 2299 2300 handleSelectSourceRequest(); 2301 return; 2302 } 2303 2304 synchronized (mSourceSyncRequestsQueue) { 2305 // updates syncHandle, advSid 2306 // set other fields as invalid or null 2307 updatePeriodicAdvertisementResultMap( 2308 device, 2309 BassConstants.INVALID_ADV_ADDRESS_TYPE, 2310 syncHandle, 2311 advertisingSid, 2312 BassConstants.INVALID_ADV_INTERVAL, 2313 BassConstants.INVALID_BROADCAST_ID, 2314 null, 2315 null); 2316 addActiveSyncedSource(syncHandle); 2317 2318 if (!leaudioBroadcastResyncHelper()) { 2319 synchronized (mSearchScanCallbackLock) { 2320 // when searching is stopped then start timer to stop active syncs 2321 if (!isSearchInProgress()) { 2322 mHandler.removeMessages(MESSAGE_SYNC_TIMEOUT); 2323 log("Started MESSAGE_SYNC_TIMEOUT"); 2324 mHandler.sendEmptyMessageDelayed( 2325 MESSAGE_SYNC_TIMEOUT, sSyncActiveTimeout.toMillis()); 2326 } 2327 } 2328 } else { 2329 mTimeoutHandler.stop(broadcastId, MESSAGE_BROADCAST_MONITOR_TIMEOUT); 2330 } 2331 2332 // update valid sync handle in mPeriodicAdvCallbacksMap 2333 if (mPeriodicAdvCallbacksMap.containsKey(BassConstants.PENDING_SYNC_HANDLE)) { 2334 PeriodicAdvertisingCallback paCb = 2335 mPeriodicAdvCallbacksMap.get(BassConstants.PENDING_SYNC_HANDLE); 2336 mPeriodicAdvCallbacksMap.put(syncHandle, paCb); 2337 mPeriodicAdvCallbacksMap.remove(BassConstants.PENDING_SYNC_HANDLE); 2338 } 2339 2340 mBisDiscoveryCounterMap.put(syncHandle, MAX_BIS_DISCOVERY_TRIES_NUM); 2341 } 2342 synchronized (mSinksWaitingForPast) { 2343 Iterator<Map.Entry<BluetoothDevice, Pair<Integer, Integer>>> iterator = 2344 mSinksWaitingForPast.entrySet().iterator(); 2345 while (iterator.hasNext()) { 2346 Map.Entry<BluetoothDevice, Pair<Integer, Integer>> entry = iterator.next(); 2347 BluetoothDevice sinkDevice = entry.getKey(); 2348 int broadcastIdForPast = entry.getValue().first; 2349 if (broadcastId == broadcastIdForPast) { 2350 int sourceId = entry.getValue().second; 2351 synchronized (mStateMachines) { 2352 BassClientStateMachine sm = getOrCreateStateMachine(sinkDevice); 2353 Message message = 2354 sm.obtainMessage( 2355 BassClientStateMachine.INITIATE_PA_SYNC_TRANSFER); 2356 message.arg1 = syncHandle; 2357 message.arg2 = sourceId; 2358 sm.sendMessage(message); 2359 } 2360 synchronized (mPendingSourcesToAdd) { 2361 Iterator<AddSourceData> addIterator = mPendingSourcesToAdd.iterator(); 2362 while (addIterator.hasNext()) { 2363 AddSourceData pendingSourcesToAdd = addIterator.next(); 2364 if (pendingSourcesToAdd.sourceMetadata.getBroadcastId() 2365 == broadcastId 2366 && pendingSourcesToAdd.sink.equals(sinkDevice)) { 2367 addIterator.remove(); 2368 } 2369 } 2370 } 2371 iterator.remove(); 2372 } 2373 } 2374 } 2375 synchronized (mPendingSourcesToAdd) { 2376 List<AddSourceData> pendingSourcesToAdd = new ArrayList<>(); 2377 Iterator<AddSourceData> iterator = mPendingSourcesToAdd.iterator(); 2378 while (iterator.hasNext()) { 2379 AddSourceData pendingSourceToAdd = iterator.next(); 2380 if (pendingSourceToAdd.sourceMetadata.getBroadcastId() == broadcastId) { 2381 boolean addSource = true; 2382 if (pendingSourceToAdd.isGroupOp && !pendingSourcesToAdd.isEmpty()) { 2383 List<BluetoothDevice> deviceGroup = 2384 getTargetDeviceList( 2385 pendingSourceToAdd.sink, /* isGroupOp */ true); 2386 for (AddSourceData addSourceData : pendingSourcesToAdd) { 2387 if (addSourceData.isGroupOp 2388 && deviceGroup.contains(addSourceData.sink)) { 2389 addSource = false; 2390 } 2391 } 2392 } 2393 if (addSource) { 2394 pendingSourcesToAdd.add(pendingSourceToAdd); 2395 } 2396 iterator.remove(); 2397 } 2398 } 2399 for (AddSourceData addSourceData : pendingSourcesToAdd) { 2400 addSource( 2401 addSourceData.sink, 2402 addSourceData.sourceMetadata, 2403 addSourceData.isGroupOp); 2404 } 2405 } 2406 handleSelectSourceRequest(); 2407 } 2408 2409 @Override 2410 public void onPeriodicAdvertisingReport(PeriodicAdvertisingReport report) { 2411 int syncHandle = report.getSyncHandle(); 2412 log("onPeriodicAdvertisingReport " + syncHandle); 2413 Integer bisCounter = mBisDiscoveryCounterMap.get(syncHandle); 2414 2415 // Parse the BIS indices from report's service data 2416 if (bisCounter != null && bisCounter != 0) { 2417 if (parseScanRecord(syncHandle, report.getData())) { 2418 mBisDiscoveryCounterMap.put(syncHandle, 0); 2419 } else { 2420 bisCounter--; 2421 mBisDiscoveryCounterMap.put(syncHandle, bisCounter); 2422 if (bisCounter == 0) { 2423 cancelActiveSync(syncHandle); 2424 } 2425 } 2426 } 2427 2428 if (leaudioBigDependsOnAudioState()) { 2429 BluetoothDevice srcDevice = getDeviceForSyncHandle(syncHandle); 2430 if (srcDevice == null) { 2431 log("No device found."); 2432 return; 2433 } 2434 PeriodicAdvertisementResult result = 2435 getPeriodicAdvertisementResult( 2436 srcDevice, getBroadcastIdForSyncHandle(syncHandle)); 2437 if (result == null) { 2438 log("No PA record found"); 2439 return; 2440 } 2441 BaseData baseData = getBase(syncHandle); 2442 if (baseData == null) { 2443 log("No BaseData found"); 2444 return; 2445 } 2446 PublicBroadcastData pbData = result.getPublicBroadcastData(); 2447 if (pbData == null) { 2448 log("No public broadcast data found, wait for BIG"); 2449 return; 2450 } 2451 if (!result.isNotified()) { 2452 result.setNotified(true); 2453 BluetoothLeBroadcastMetadata metaData = 2454 getBroadcastMetadataFromBaseData( 2455 baseData, srcDevice, syncHandle, pbData.isEncrypted()); 2456 log("Notify broadcast source found"); 2457 mCallbacks.notifySourceFound(metaData); 2458 } 2459 } 2460 } 2461 2462 @Override 2463 public void onSyncLost(int syncHandle) { 2464 int broadcastId = getBroadcastIdForSyncHandle(syncHandle); 2465 log("OnSyncLost: syncHandle=" + syncHandle + ", broadcastID=" + broadcastId); 2466 clearAllDataForSyncHandle(syncHandle); 2467 if (broadcastId != BassConstants.INVALID_BROADCAST_ID) { 2468 synchronized (mSourceSyncRequestsQueue) { 2469 int failsCounter = mSyncFailureCounter.getOrDefault(broadcastId, 0) + 1; 2470 mSyncFailureCounter.put(broadcastId, failsCounter); 2471 } 2472 mTimeoutHandler.stop(broadcastId, MESSAGE_SYNC_LOST_TIMEOUT); 2473 if (isSinkUnintentionalPauseType(broadcastId)) { 2474 if (!mTimeoutHandler.isStarted( 2475 broadcastId, MESSAGE_BROADCAST_MONITOR_TIMEOUT)) { 2476 mTimeoutHandler.start( 2477 broadcastId, 2478 MESSAGE_BROADCAST_MONITOR_TIMEOUT, 2479 sBroadcasterMonitorTimeout); 2480 } 2481 addSelectSourceRequest(broadcastId, /* hasPriority */ true); 2482 } else { 2483 if (leaudioBroadcastResyncHelper()) { 2484 mTimeoutHandler.start( 2485 broadcastId, MESSAGE_SYNC_LOST_TIMEOUT, sSyncLostTimeout); 2486 } else { 2487 // Clear from cache to make possible sync again (only during active 2488 // searching) 2489 synchronized (mSearchScanCallbackLock) { 2490 if (isSearchInProgress()) { 2491 mCachedBroadcasts.remove(broadcastId); 2492 } 2493 } 2494 log("Notify broadcast source lost, broadcast id: " + broadcastId); 2495 mCallbacks.notifySourceLost(broadcastId); 2496 } 2497 } 2498 } 2499 } 2500 2501 @Override 2502 public void onBigInfoAdvertisingReport(int syncHandle, boolean encrypted) { 2503 log( 2504 "onBIGInfoAdvertisingReport: syncHandle=" 2505 + syncHandle 2506 + ", encrypted =" 2507 + encrypted); 2508 BluetoothDevice srcDevice = getDeviceForSyncHandle(syncHandle); 2509 if (srcDevice == null) { 2510 log("No device found."); 2511 return; 2512 } 2513 int broadcastId = getBroadcastIdForSyncHandle(syncHandle); 2514 PeriodicAdvertisementResult result = 2515 getPeriodicAdvertisementResult(srcDevice, broadcastId); 2516 if (result == null) { 2517 log("No PA record found"); 2518 return; 2519 } 2520 BaseData baseData = getBase(syncHandle); 2521 if (baseData == null) { 2522 log("No BaseData found"); 2523 return; 2524 } 2525 if (!result.isNotified()) { 2526 result.setNotified(true); 2527 BluetoothLeBroadcastMetadata metaData = 2528 getBroadcastMetadataFromBaseData( 2529 baseData, srcDevice, syncHandle, encrypted); 2530 log("Notify broadcast source found"); 2531 mCallbacks.notifySourceFound(metaData); 2532 } 2533 if (isSinkUnintentionalPauseType(broadcastId)) { 2534 resumeReceiversSourceSynchronization(); 2535 } 2536 } 2537 } 2538 2539 private void clearAllDataForSyncHandle(Integer syncHandle) { 2540 synchronized (mSourceSyncRequestsQueue) { 2541 removeActiveSyncedSource(syncHandle); 2542 mPeriodicAdvCallbacksMap.remove(syncHandle); 2543 mSyncHandleToBaseDataMap.remove(syncHandle); 2544 mBisDiscoveryCounterMap.remove(syncHandle); 2545 BluetoothDevice srcDevice = getDeviceForSyncHandle(syncHandle); 2546 mSyncHandleToDeviceMap.remove(syncHandle); 2547 int broadcastId = getBroadcastIdForSyncHandle(syncHandle); 2548 synchronized (mPendingSourcesToAdd) { 2549 mPendingSourcesToAdd.removeIf( 2550 pendingSourcesToAdd -> 2551 pendingSourcesToAdd.sourceMetadata.getBroadcastId() == broadcastId); 2552 } 2553 synchronized (mSinksWaitingForPast) { 2554 mSinksWaitingForPast 2555 .entrySet() 2556 .removeIf(entry -> entry.getValue().first == broadcastId); 2557 } 2558 mSyncHandleToBroadcastIdMap.remove(syncHandle); 2559 if (srcDevice != null) { 2560 mPeriodicAdvertisementResultMap.get(srcDevice).remove(broadcastId); 2561 if (mPeriodicAdvertisementResultMap.get(srcDevice).isEmpty()) { 2562 mPeriodicAdvertisementResultMap.remove(srcDevice); 2563 } 2564 } 2565 } 2566 } 2567 2568 private BluetoothLeBroadcastMetadata getBroadcastMetadataFromBaseData( 2569 BaseData baseData, BluetoothDevice device, int syncHandle, boolean encrypted) { 2570 BluetoothLeBroadcastMetadata.Builder metaData = new BluetoothLeBroadcastMetadata.Builder(); 2571 int index = 0; 2572 for (BaseData.BaseInformation baseLevel2 : baseData.levelTwo()) { 2573 BluetoothLeBroadcastSubgroup.Builder subGroup = 2574 new BluetoothLeBroadcastSubgroup.Builder(); 2575 for (int j = 0; j < baseLevel2.mNumSubGroups; j++) { 2576 BaseData.BaseInformation baseLevel3 = baseData.levelThree().get(index++); 2577 BluetoothLeBroadcastChannel.Builder channel = 2578 new BluetoothLeBroadcastChannel.Builder(); 2579 channel.setChannelIndex(baseLevel3.mIndex); 2580 channel.setSelected(false); 2581 try { 2582 channel.setCodecMetadata( 2583 BluetoothLeAudioCodecConfigMetadata.fromRawBytes( 2584 baseLevel3.mCodecConfigInfo)); 2585 } catch (IllegalArgumentException e) { 2586 Log.w(TAG, "Invalid metadata, adding empty data. Error: " + e); 2587 channel.setCodecMetadata( 2588 BluetoothLeAudioCodecConfigMetadata.fromRawBytes(new byte[0])); 2589 } 2590 subGroup.addChannel(channel.build()); 2591 } 2592 byte[] arrayCodecId = baseLevel2.mCodecId; 2593 long codeId = 2594 ((long) (arrayCodecId[4] & 0xff)) << 32 2595 | (arrayCodecId[3] & 0xff) << 24 2596 | (arrayCodecId[2] & 0xff) << 16 2597 | (arrayCodecId[1] & 0xff) << 8 2598 | (arrayCodecId[0] & 0xff); 2599 subGroup.setCodecId(codeId); 2600 try { 2601 subGroup.setCodecSpecificConfig( 2602 BluetoothLeAudioCodecConfigMetadata.fromRawBytes( 2603 baseLevel2.mCodecConfigInfo)); 2604 } catch (IllegalArgumentException e) { 2605 Log.w(TAG, "Invalid config, adding empty one. Error: " + e); 2606 subGroup.setCodecSpecificConfig( 2607 BluetoothLeAudioCodecConfigMetadata.fromRawBytes(new byte[0])); 2608 } 2609 2610 try { 2611 subGroup.setContentMetadata( 2612 BluetoothLeAudioContentMetadata.fromRawBytes(baseLevel2.mMetaData)); 2613 } catch (IllegalArgumentException e) { 2614 Log.w(TAG, "Invalid metadata, adding empty one. Error: " + e); 2615 subGroup.setContentMetadata( 2616 BluetoothLeAudioContentMetadata.fromRawBytes(new byte[0])); 2617 } 2618 2619 metaData.addSubgroup(subGroup.build()); 2620 } 2621 metaData.setSourceDevice(device, device.getAddressType()); 2622 byte[] arrayPresentationDelay = baseData.levelOne().mPresentationDelay; 2623 int presentationDelay = 2624 (int) 2625 ((arrayPresentationDelay[2] & 0xff) << 16 2626 | (arrayPresentationDelay[1] & 0xff) << 8 2627 | (arrayPresentationDelay[0] & 0xff)); 2628 metaData.setPresentationDelayMicros(presentationDelay); 2629 PeriodicAdvertisementResult result = 2630 getPeriodicAdvertisementResult(device, getBroadcastIdForSyncHandle(syncHandle)); 2631 if (result != null) { 2632 int broadcastId = result.getBroadcastId(); 2633 log("broadcast ID: " + broadcastId); 2634 metaData.setBroadcastId(broadcastId); 2635 metaData.setSourceAdvertisingSid(result.getAdvSid()); 2636 2637 PublicBroadcastData pbData = result.getPublicBroadcastData(); 2638 if (pbData != null) { 2639 metaData.setPublicBroadcast(true); 2640 metaData.setAudioConfigQuality(pbData.getAudioConfigQuality()); 2641 try { 2642 metaData.setPublicBroadcastMetadata( 2643 BluetoothLeAudioContentMetadata.fromRawBytes(pbData.getMetadata())); 2644 } catch (IllegalArgumentException e) { 2645 Log.w(TAG, "Invalid public metadata, adding empty one. Error " + e); 2646 metaData.setPublicBroadcastMetadata(null); 2647 } 2648 } 2649 2650 String broadcastName = result.getBroadcastName(); 2651 if (broadcastName != null) { 2652 metaData.setBroadcastName(broadcastName); 2653 } 2654 2655 // update the rssi value 2656 ScanResult scanRes = getCachedBroadcast(broadcastId); 2657 if (scanRes != null) { 2658 metaData.setRssi(scanRes.getRssi()); 2659 } 2660 } 2661 metaData.setEncrypted(encrypted); 2662 2663 return metaData.build(); 2664 } 2665 2666 /** 2667 * @param syncHandle syncHandle to unsync source and clean up all data for it. Null is used to 2668 * clean up all pending and established broadcast syncs. 2669 */ 2670 private void cancelActiveSync(Integer syncHandle) { 2671 log("cancelActiveSync: syncHandle = " + syncHandle); 2672 if (syncHandle == null 2673 || (leaudioBroadcastResyncHelper() 2674 && syncHandle == BassConstants.PENDING_SYNC_HANDLE)) { 2675 // cancel the pending sync request 2676 unsyncSource(BassConstants.PENDING_SYNC_HANDLE); 2677 } 2678 List<Integer> activeSyncedSrc = new ArrayList<>(getActiveSyncedSources()); 2679 2680 /* Stop sync if there is some running */ 2681 if (!activeSyncedSrc.isEmpty() 2682 && (syncHandle == null || activeSyncedSrc.contains(syncHandle))) { 2683 if (syncHandle != null) { 2684 // only one source needs to be unsynced 2685 unsyncSource(syncHandle); 2686 } else { 2687 // unsync all the sources 2688 for (int handle : activeSyncedSrc) { 2689 unsyncSource(handle); 2690 } 2691 } 2692 } 2693 printAllSyncData(); 2694 } 2695 2696 @SuppressLint("AndroidFrameworkRequiresPermission") // TODO: b/350563786 - Fix BASS annotation 2697 private boolean unsyncSource(int syncHandle) { 2698 log("unsyncSource: syncHandle: " + syncHandle); 2699 if (mPeriodicAdvCallbacksMap.containsKey(syncHandle)) { 2700 try { 2701 BluetoothMethodProxy.getInstance() 2702 .periodicAdvertisingManagerUnregisterSync( 2703 BassClientPeriodicAdvertisingManager 2704 .getPeriodicAdvertisingManager(), 2705 mPeriodicAdvCallbacksMap.get(syncHandle)); 2706 } catch (IllegalArgumentException ex) { 2707 Log.e(TAG, "unregisterSync:IllegalArgumentException"); 2708 return false; 2709 } 2710 } else { 2711 log("calling unregisterSync, not found syncHandle: " + syncHandle); 2712 } 2713 clearAllDataForSyncHandle(syncHandle); 2714 return true; 2715 } 2716 2717 boolean parseBaseData(int syncHandle, byte[] serviceData) { 2718 log("parseBaseData" + Arrays.toString(serviceData)); 2719 BaseData base = BaseData.parseBaseData(serviceData); 2720 if (base != null) { 2721 updateBase(syncHandle, base); 2722 base.print(); 2723 return true; 2724 } else { 2725 Log.e(TAG, "Seems BASE is not in parsable format"); 2726 } 2727 return false; 2728 } 2729 2730 boolean parseScanRecord(int syncHandle, ScanRecord record) { 2731 int broadcastId = getBroadcastIdForSyncHandle(syncHandle); 2732 log( 2733 "parseScanRecord: syncHandle=" 2734 + syncHandle 2735 + ", broadcastID=" 2736 + broadcastId 2737 + ", record=" 2738 + record); 2739 Map<ParcelUuid, byte[]> bmsAdvDataMap = record.getServiceData(); 2740 if (bmsAdvDataMap != null) { 2741 for (Map.Entry<ParcelUuid, byte[]> entry : bmsAdvDataMap.entrySet()) { 2742 log( 2743 "ParcelUUid = " 2744 + entry.getKey() 2745 + ", Value = " 2746 + Arrays.toString(entry.getValue())); 2747 } 2748 } 2749 byte[] advData = record.getServiceData(BassConstants.BASIC_AUDIO_UUID); 2750 if (advData != null) { 2751 return parseBaseData(syncHandle, advData); 2752 } else { 2753 Log.e(TAG, "No service data in Scan record"); 2754 } 2755 return false; 2756 } 2757 2758 void addSelectSourceRequest(int broadcastId, boolean hasPriority) { 2759 if (getActiveSyncedSources().contains(getSyncHandleForBroadcastId(broadcastId))) { 2760 log("addSelectSourceRequest: Already synced"); 2761 return; 2762 } 2763 2764 if (isAddedToSelectSourceRequest(broadcastId, hasPriority)) { 2765 log("addSelectSourceRequest: Already added"); 2766 return; 2767 } 2768 2769 sEventLogger.logd( 2770 TAG, 2771 "Add Select Broadcast Source, broadcastId: " 2772 + broadcastId 2773 + ", hasPriority: " 2774 + hasPriority); 2775 2776 ScanResult scanRes = getCachedBroadcast(broadcastId); 2777 if (scanRes == null) { 2778 log("addSelectSourceRequest: ScanResult empty"); 2779 return; 2780 } 2781 2782 ScanRecord scanRecord = scanRes.getScanRecord(); 2783 if (scanRecord == null) { 2784 log("addSelectSourceRequest: ScanRecord empty"); 2785 return; 2786 } 2787 2788 mTimeoutHandler.stop(broadcastId, MESSAGE_SYNC_LOST_TIMEOUT); 2789 synchronized (mSourceSyncRequestsQueue) { 2790 if (!mSyncFailureCounter.containsKey(broadcastId)) { 2791 mSyncFailureCounter.put(broadcastId, 0); 2792 } 2793 mSourceSyncRequestsQueue.add( 2794 new SourceSyncRequest( 2795 scanRes, hasPriority, mSyncFailureCounter.get(broadcastId))); 2796 } 2797 2798 handleSelectSourceRequest(); 2799 } 2800 2801 @SuppressLint("AndroidFrameworkRequiresPermission") // TODO: b/350563786 - Fix BASS annotation 2802 private void handleSelectSourceRequest() { 2803 PeriodicAdvertisingCallback paCb; 2804 ScanResult scanRes; 2805 int broadcastId = BassConstants.INVALID_BROADCAST_ID; 2806 PublicBroadcastData pbData = null; 2807 List<Integer> activeSyncedSrc; 2808 String broadcastName; 2809 2810 synchronized (mSourceSyncRequestsQueue) { 2811 if (mSourceSyncRequestsQueue.isEmpty()) { 2812 return; 2813 } else if (mPeriodicAdvCallbacksMap.containsKey(BassConstants.PENDING_SYNC_HANDLE)) { 2814 log("handleSelectSourceRequest: already pending sync"); 2815 return; 2816 } 2817 2818 scanRes = mSourceSyncRequestsQueue.poll().scanResult; 2819 ScanRecord scanRecord = scanRes.getScanRecord(); 2820 2821 sEventLogger.logd(TAG, "Select Broadcast Source, result: " + scanRes); 2822 2823 broadcastId = BassUtils.getBroadcastId(scanRecord); 2824 if (broadcastId == BassConstants.INVALID_BROADCAST_ID) { 2825 Log.e(TAG, "Invalid broadcast ID"); 2826 handleSelectSourceRequest(); 2827 return; 2828 } 2829 2830 // Avoid duplicated sync request if the same broadcast BIG is synced 2831 activeSyncedSrc = new ArrayList<>(getActiveSyncedSources()); 2832 if (activeSyncedSrc.contains(getSyncHandleForBroadcastId(broadcastId))) { 2833 log("Skip duplicated sync request to broadcast id: " + broadcastId); 2834 handleSelectSourceRequest(); 2835 return; 2836 } 2837 2838 pbData = BassUtils.getPublicBroadcastData(scanRecord); 2839 broadcastName = BassUtils.getBroadcastName(scanRecord); 2840 paCb = new PACallback(); 2841 // put PENDING_SYNC_HANDLE and update it in onSyncEstablished 2842 mPeriodicAdvCallbacksMap.put(BassConstants.PENDING_SYNC_HANDLE, paCb); 2843 updatePeriodicAdvertisementResultMap( 2844 scanRes.getDevice(), 2845 scanRes.getDevice().getAddressType(), 2846 BassConstants.PENDING_SYNC_HANDLE, 2847 BassConstants.INVALID_ADV_SID, 2848 scanRes.getPeriodicAdvertisingInterval(), 2849 broadcastId, 2850 pbData, 2851 broadcastName); 2852 } 2853 2854 // Check if there are resources for sync 2855 if (activeSyncedSrc.size() >= MAX_ACTIVE_SYNCED_SOURCES_NUM) { 2856 log("handleSelectSourceRequest: reached max allowed active source"); 2857 if (!leaudioBroadcastResyncHelper()) { 2858 int syncHandle = activeSyncedSrc.get(0); 2859 // removing the 1st synced source before proceeding to add new 2860 cancelActiveSync(syncHandle); 2861 } else { 2862 Boolean canceledActiveSync = false; 2863 int broadcastIdToLostMonitoring = BassConstants.INVALID_BROADCAST_ID; 2864 for (int syncHandle : activeSyncedSrc) { 2865 if (!isAnyReceiverSyncedToBroadcast(getBroadcastIdForSyncHandle(syncHandle))) { 2866 canceledActiveSync = true; 2867 broadcastIdToLostMonitoring = getBroadcastIdForSyncHandle(syncHandle); 2868 cancelActiveSync(syncHandle); 2869 break; 2870 } 2871 } 2872 if (!canceledActiveSync) { 2873 int syncHandle = activeSyncedSrc.get(0); 2874 // removing the 1st synced source before proceeding to add new 2875 broadcastIdToLostMonitoring = getBroadcastIdForSyncHandle(syncHandle); 2876 cancelActiveSync(syncHandle); 2877 } 2878 mTimeoutHandler.start( 2879 broadcastIdToLostMonitoring, MESSAGE_SYNC_LOST_TIMEOUT, sSyncLostTimeout); 2880 } 2881 } 2882 2883 try { 2884 BluetoothMethodProxy.getInstance() 2885 .periodicAdvertisingManagerRegisterSync( 2886 BassClientPeriodicAdvertisingManager.getPeriodicAdvertisingManager(), 2887 scanRes, 2888 0, 2889 BassConstants.PSYNC_TIMEOUT, 2890 paCb, 2891 null); 2892 } catch (IllegalArgumentException ex) { 2893 Log.e(TAG, "registerSync:IllegalArgumentException"); 2894 clearAllDataForSyncHandle(BassConstants.PENDING_SYNC_HANDLE); 2895 handleSelectSourceRequest(); 2896 return; 2897 } 2898 } 2899 2900 private void storeSinkMetadata( 2901 BluetoothDevice device, int broadcastId, BluetoothLeBroadcastMetadata metadata) { 2902 if (device == null 2903 || broadcastId == BassConstants.INVALID_BROADCAST_ID 2904 || metadata == null) { 2905 Log.e( 2906 TAG, 2907 "Failed to store Sink Metadata, invalid parameters (device: " 2908 + device 2909 + ", broadcastId: " 2910 + broadcastId 2911 + ", metadata: " 2912 + metadata 2913 + ")"); 2914 return; 2915 } 2916 2917 mBroadcastMetadataMap.compute( 2918 device, 2919 (key, existingMap) -> { 2920 if (existingMap == null) { 2921 existingMap = new ConcurrentHashMap<>(); 2922 } 2923 existingMap.put(broadcastId, metadata); 2924 return existingMap; 2925 }); 2926 } 2927 2928 private void removeSinkMetadataHelper(BluetoothDevice device, int broadcastId) { 2929 mBroadcastMetadataMap.compute( 2930 device, 2931 (key, existingMap) -> { 2932 if (existingMap != null) { 2933 existingMap.remove(broadcastId); 2934 if (existingMap.isEmpty()) { 2935 return null; 2936 } 2937 } else { 2938 Log.d( 2939 TAG, 2940 "There is no metadata related to sink (device: " 2941 + device 2942 + ", broadcastId: " 2943 + broadcastId); 2944 } 2945 return existingMap; 2946 }); 2947 } 2948 2949 private void removeSinkMetadata(BluetoothDevice device, int broadcastId) { 2950 if (device == null || broadcastId == BassConstants.INVALID_BROADCAST_ID) { 2951 Log.e( 2952 TAG, 2953 "Failed to remove Sink Metadata, invalid parameters (device: " 2954 + device 2955 + ", broadcastId: " 2956 + broadcastId 2957 + ")"); 2958 return; 2959 } 2960 2961 removeSinkMetadataHelper(device, broadcastId); 2962 removeSinkMetadataFromGroupIfWholeUnsynced(device, broadcastId); 2963 } 2964 2965 private void removeSinkMetadata(BluetoothDevice device) { 2966 if (device == null) { 2967 Log.e( 2968 TAG, 2969 "Failed to remove Sink Metadata, invalid parameters (device: " + device + ")"); 2970 return; 2971 } 2972 2973 mBroadcastMetadataMap.remove(device); 2974 removeSinkMetadataFromGroupIfWholeUnsynced(device); 2975 } 2976 2977 /** 2978 * Removes sink metadata from a group if all other sinks (except the given device) are unsynced 2979 * from the given broadcast and not paused by the host. If this condition is met, sink metadata 2980 * is removed from the entire group, including the given device. 2981 * 2982 * @param device The Bluetooth device for which group synchronization with the broadcast should 2983 * be checked. The given device is skipped in the check because even if its sink metadata 2984 * has been removed, it may still be synchronized with the broadcast. 2985 * @param broadcastId The broadcast ID to check against. 2986 */ 2987 private void removeSinkMetadataFromGroupIfWholeUnsynced( 2988 BluetoothDevice device, int broadcastId) { 2989 if (device == null || broadcastId == BassConstants.INVALID_BROADCAST_ID) { 2990 Log.e( 2991 TAG, 2992 "Failed to remove Sink Metadata, invalid parameters (device: " 2993 + device 2994 + ", broadcastId: " 2995 + broadcastId 2996 + ")"); 2997 return; 2998 } 2999 3000 List<BluetoothDevice> sinks = getTargetDeviceList(device, /* isGroupOp */ true); 3001 boolean removeSinks = true; 3002 // Check if all others sinks than this device are unsynced and not paused by host 3003 // This device is removed or should be removed, so it has to be skipped in that check 3004 for (BluetoothDevice sink : sinks) { 3005 if (sink.equals(device)) { 3006 continue; 3007 } 3008 if (getAllSources(sink).stream().anyMatch(rs -> (rs.getBroadcastId() == broadcastId)) 3009 || (isHostPauseType(broadcastId) && !mPausedBroadcastSinks.isEmpty())) { 3010 removeSinks = false; 3011 break; 3012 } 3013 } 3014 // Then remove such metadata from all of them 3015 if (removeSinks) { 3016 for (BluetoothDevice sink : sinks) { 3017 removeSinkMetadataHelper(sink, broadcastId); 3018 } 3019 } 3020 } 3021 3022 /** 3023 * Removes sink metadata from a group if all other sinks (except the given device) are unsynced 3024 * from any broadcast and not paused by the host. If this condition is met, sink metadata is 3025 * removed from the entire group, including the given device. 3026 * 3027 * @param device The Bluetooth device for which group synchronization with the broadcasts should 3028 * be checked. The given device is skipped in the check because even if its sink metadata 3029 * has been removed, it may still be synchronized with the broadcast. 3030 */ 3031 private void removeSinkMetadataFromGroupIfWholeUnsynced(BluetoothDevice device) { 3032 if (device == null) { 3033 Log.e( 3034 TAG, 3035 "Failed to remove Sink Metadata, invalid parameter (device: " + device + ")"); 3036 return; 3037 } 3038 3039 List<BluetoothDevice> sinks = getTargetDeviceList(device, /* isGroupOp */ true); 3040 // Check sync for broadcastIds from all sinks in group as device could be already removed 3041 for (BluetoothDevice sink : sinks) { 3042 List<Integer> broadcastIds = 3043 new ArrayList<>( 3044 mBroadcastMetadataMap 3045 .getOrDefault(sink, Collections.emptyMap()) 3046 .keySet()); 3047 // Check all broadcastIds sync for each sink and remove metadata if group unsynced 3048 for (Integer broadcastId : broadcastIds) { 3049 // The device is used intentionally instead of a sink, even if we use broadcastIds 3050 // from other sinks 3051 removeSinkMetadataFromGroupIfWholeUnsynced(device, broadcastId); 3052 } 3053 } 3054 } 3055 3056 private void checkIfBroadcastIsSuspendedBySourceRemovalAndClearData( 3057 BluetoothDevice device, BassClientStateMachine stateMachine) { 3058 if (!mPausedBroadcastSinks.contains(device)) { 3059 return; 3060 } 3061 Map<Integer, BluetoothLeBroadcastMetadata> entry = mBroadcastMetadataMap.get(device); 3062 if (entry == null) { 3063 return; 3064 } 3065 if (entry.keySet().size() >= stateMachine.getMaximumSourceCapacity()) { 3066 for (Integer broadcastId : entry.keySet()) { 3067 // Found broadcastId which is paused by host but not synced 3068 if (!getAllSources(device).stream() 3069 .anyMatch(rs -> (rs.getBroadcastId() == broadcastId)) 3070 && isHostPauseType(broadcastId)) { 3071 stopBigMonitoring(broadcastId, /* hostInitiated */ false); 3072 removeSinkMetadata(device, broadcastId); 3073 return; 3074 } 3075 } 3076 } 3077 } 3078 3079 private Boolean isAddedToSelectSourceRequest(int broadcastId, boolean priorityImportant) { 3080 synchronized (mSourceSyncRequestsQueue) { 3081 if (getBroadcastIdForSyncHandle(BassConstants.PENDING_SYNC_HANDLE) == broadcastId) { 3082 return true; 3083 } 3084 3085 for (SourceSyncRequest sourceSyncRequest : mSourceSyncRequestsQueue) { 3086 if (BassUtils.getBroadcastId(sourceSyncRequest.scanResult) == broadcastId) { 3087 return !priorityImportant || sourceSyncRequest.hasPriority; 3088 } 3089 } 3090 } 3091 3092 return false; 3093 } 3094 3095 /** 3096 * Add a Broadcast Source to the Broadcast Sink 3097 * 3098 * @param sink Broadcast Sink to which the Broadcast Source should be added 3099 * @param sourceMetadata Broadcast Source metadata to be added to the Broadcast Sink 3100 * @param isGroupOp set to true If Application wants to perform this operation for all 3101 * coordinated set members, False otherwise 3102 */ 3103 public void addSource( 3104 BluetoothDevice sink, BluetoothLeBroadcastMetadata sourceMetadata, boolean isGroupOp) { 3105 log( 3106 "addSource: " 3107 + ("device: " + sink) 3108 + (", sourceMetadata: " + sourceMetadata) 3109 + (", isGroupOp: " + isGroupOp)); 3110 3111 List<BluetoothDevice> devices = getTargetDeviceList(sink, /* isGroupOp */ isGroupOp); 3112 // Don't coordinate it as a group if there's no group or there is one device only 3113 if (devices.size() < 2) { 3114 isGroupOp = false; 3115 } 3116 3117 if (sourceMetadata == null) { 3118 log("addSource: Error bad parameter: sourceMetadata cannot be null"); 3119 return; 3120 } 3121 3122 if (isLocalBroadcast(sourceMetadata)) { 3123 LeAudioService leAudioService = mServiceFactory.getLeAudioService(); 3124 if (leaudioBigDependsOnAudioState()) { 3125 if (leAudioService == null 3126 || !(leAudioService.isPaused(sourceMetadata.getBroadcastId()) 3127 || leAudioService.isPlaying(sourceMetadata.getBroadcastId()))) { 3128 Log.w(TAG, "addSource: Local source can't be add"); 3129 3130 mCallbacks.notifySourceAddFailed( 3131 sink, 3132 sourceMetadata, 3133 BluetoothStatusCodes.ERROR_LOCAL_NOT_ENOUGH_RESOURCES); 3134 3135 return; 3136 } 3137 } else { 3138 if (leAudioService == null 3139 || !leAudioService.isPlaying(sourceMetadata.getBroadcastId())) { 3140 Log.w(TAG, "addSource: Local source can't be add"); 3141 3142 mCallbacks.notifySourceAddFailed( 3143 sink, 3144 sourceMetadata, 3145 BluetoothStatusCodes.ERROR_LOCAL_NOT_ENOUGH_RESOURCES); 3146 3147 return; 3148 } 3149 } 3150 } 3151 3152 // Remove pausedBroadcastId in case that broadcast was paused before. 3153 mPausedBroadcastIds.remove(sourceMetadata.getBroadcastId()); 3154 logPausedBroadcastsAndSinks(); 3155 3156 for (BluetoothDevice device : devices) { 3157 BluetoothDevice sourceDevice = sourceMetadata.getSourceDevice(); 3158 if (!isLocalBroadcast(sourceMetadata) 3159 && (!getActiveSyncedSources() 3160 .contains( 3161 getSyncHandleForBroadcastId( 3162 sourceMetadata.getBroadcastId())))) { 3163 log("Adding inactive source: " + sourceDevice); 3164 int broadcastId = sourceMetadata.getBroadcastId(); 3165 if (broadcastId != BassConstants.INVALID_BROADCAST_ID) { 3166 // Check if not added already 3167 if (isAddedToSelectSourceRequest(broadcastId, /* priorityImportant */ true)) { 3168 mPendingSourcesToAdd.add( 3169 new AddSourceData(device, sourceMetadata, isGroupOp)); 3170 // If the source has been synced before, try to re-sync 3171 // with the source by previously cached scan result. 3172 } else if (getCachedBroadcast(broadcastId) != null) { 3173 mPendingSourcesToAdd.add( 3174 new AddSourceData(device, sourceMetadata, isGroupOp)); 3175 addSelectSourceRequest(broadcastId, /* hasPriority */ true); 3176 } else { 3177 Log.w(TAG, "AddSource: broadcast not cached, broadcastId: " + broadcastId); 3178 mCallbacks.notifySourceAddFailed( 3179 sink, sourceMetadata, BluetoothStatusCodes.ERROR_BAD_PARAMETERS); 3180 return; 3181 } 3182 } else { 3183 Log.w(TAG, "AddSource: invalid broadcastId"); 3184 mCallbacks.notifySourceAddFailed( 3185 sink, sourceMetadata, BluetoothStatusCodes.ERROR_BAD_PARAMETERS); 3186 return; 3187 } 3188 continue; 3189 } 3190 3191 BassClientStateMachine stateMachine = getOrCreateStateMachine(device); 3192 int statusCode = 3193 validateParametersForSourceOperation(stateMachine, device, sourceMetadata); 3194 if (statusCode != BluetoothStatusCodes.SUCCESS) { 3195 mCallbacks.notifySourceAddFailed(device, sourceMetadata, statusCode); 3196 continue; 3197 } 3198 if (!stateMachine.isBassStateReady()) { 3199 Log.d(TAG, "addSource: BASS state not ready, retry later with device: " + device); 3200 synchronized (mPendingSourcesToAdd) { 3201 mPendingSourcesToAdd.add(new AddSourceData(device, sourceMetadata, isGroupOp)); 3202 } 3203 continue; 3204 } 3205 if (stateMachine.hasPendingSourceOperation()) { 3206 Log.w( 3207 TAG, 3208 "addSource: source operation already pending, device: " 3209 + device 3210 + ", broadcastId: " 3211 + sourceMetadata.getBroadcastId()); 3212 mCallbacks.notifySourceAddFailed( 3213 device, sourceMetadata, BluetoothStatusCodes.ERROR_ALREADY_IN_TARGET_STATE); 3214 continue; 3215 } 3216 if (leaudioBroadcastResyncHelper()) { 3217 int sourceId = checkDuplicateSourceAdditionAndGetSourceId(device, sourceMetadata); 3218 if (sourceId != BassConstants.INVALID_SOURCE_ID) { 3219 updateSourceToResumeBroadcast(device, sourceId, sourceMetadata); 3220 continue; 3221 } 3222 } 3223 if (!hasRoomForBroadcastSourceAddition(device)) { 3224 log("addSource: device has no room"); 3225 Integer sourceIdToRemove = getSourceIdToRemove(device); 3226 if (sourceIdToRemove != BassConstants.INVALID_SOURCE_ID) { 3227 BluetoothLeBroadcastMetadata metaData = 3228 stateMachine.getCurrentBroadcastMetadata(sourceIdToRemove); 3229 if (metaData != null) { 3230 removeSinkMetadata(device, metaData.getBroadcastId()); 3231 3232 // Add host intentional pause if previous broadcast is different than 3233 // current 3234 if (sourceMetadata.getBroadcastId() != metaData.getBroadcastId()) { 3235 stopBigMonitoring(metaData.getBroadcastId(), /* hostInitiated */ true); 3236 } 3237 } 3238 3239 sEventLogger.logd( 3240 TAG, 3241 "Switch Broadcast Source: " 3242 + ("device: " + device) 3243 + (", old SourceId: " + sourceIdToRemove) 3244 + (", new broadcastId: " + sourceMetadata.getBroadcastId()) 3245 + (", new broadcastName: " 3246 + sourceMetadata.getBroadcastName())); 3247 3248 // new source will be added once the existing source got removed 3249 if (isGroupOp) { 3250 // mark group op for both remove and add source 3251 // so setSourceGroupManaged will be updated accordingly in callbacks 3252 enqueueSourceGroupOp( 3253 device, 3254 BassClientStateMachine.REMOVE_BCAST_SOURCE, 3255 sourceIdToRemove); 3256 enqueueSourceGroupOp( 3257 device, BassClientStateMachine.ADD_BCAST_SOURCE, sourceMetadata); 3258 } 3259 3260 /* Store metadata for sink device */ 3261 storeSinkMetadata(device, sourceMetadata.getBroadcastId(), sourceMetadata); 3262 3263 Message message = 3264 stateMachine.obtainMessage(BassClientStateMachine.SWITCH_BCAST_SOURCE); 3265 message.obj = sourceMetadata; 3266 message.arg1 = sourceIdToRemove; 3267 stateMachine.sendMessage(message); 3268 } else { 3269 mCallbacks.notifySourceAddFailed( 3270 device, 3271 sourceMetadata, 3272 BluetoothStatusCodes.ERROR_REMOTE_NOT_ENOUGH_RESOURCES); 3273 } 3274 continue; 3275 } 3276 if (!leaudioBroadcastResyncHelper()) { 3277 int sourceId = checkDuplicateSourceAdditionAndGetSourceId(device, sourceMetadata); 3278 if (sourceId != BassConstants.INVALID_SOURCE_ID) { 3279 log("addSource: not a valid broadcast source addition"); 3280 mCallbacks.notifySourceAddFailed( 3281 device, 3282 sourceMetadata, 3283 BluetoothStatusCodes.ERROR_LE_BROADCAST_ASSISTANT_DUPLICATE_ADDITION); 3284 continue; 3285 } 3286 } 3287 3288 // Even if there is a room for broadcast, it could happen that all broadcasts were 3289 // suspended via removing source. In that case, we have to found such broadcast and 3290 // remove it from metadata. 3291 checkIfBroadcastIsSuspendedBySourceRemovalAndClearData(device, stateMachine); 3292 3293 /* Store metadata for sink device */ 3294 storeSinkMetadata(device, sourceMetadata.getBroadcastId(), sourceMetadata); 3295 3296 if (isGroupOp) { 3297 enqueueSourceGroupOp( 3298 device, BassClientStateMachine.ADD_BCAST_SOURCE, sourceMetadata); 3299 } 3300 3301 if (!isLocalBroadcast(sourceMetadata)) { 3302 checkAndSetGroupAllowedContextMask(device); 3303 } 3304 3305 sEventLogger.logd( 3306 TAG, 3307 "Add Broadcast Source: " 3308 + ("device: " + device) 3309 + (", broadcastId: " + sourceMetadata.getBroadcastId()) 3310 + (", broadcastName: " + sourceMetadata.getBroadcastName()) 3311 + (", isGroupOp: " + isGroupOp)); 3312 3313 Message message = stateMachine.obtainMessage(BassClientStateMachine.ADD_BCAST_SOURCE); 3314 message.obj = sourceMetadata; 3315 stateMachine.sendMessage(message); 3316 3317 byte[] code = sourceMetadata.getBroadcastCode(); 3318 if (code != null && code.length != 0) { 3319 sEventLogger.logd( 3320 TAG, 3321 "Set Broadcast Code (Add Source context): " 3322 + ("device: " + device) 3323 + (", broadcastId: " + sourceMetadata.getBroadcastId()) 3324 + (", broadcastName: " + sourceMetadata.getBroadcastName())); 3325 3326 message = stateMachine.obtainMessage(BassClientStateMachine.SET_BCAST_CODE); 3327 message.obj = sourceMetadata; 3328 message.arg1 = BassClientStateMachine.ARGTYPE_METADATA; 3329 stateMachine.sendMessage(message); 3330 } 3331 } 3332 } 3333 3334 /** 3335 * Modify the Broadcast Source information on a Broadcast Sink 3336 * 3337 * @param sink representing the Broadcast Sink to which the Broadcast Source should be updated 3338 * @param sourceId source ID as delivered in onSourceAdded 3339 * @param updatedMetadata updated Broadcast Source metadata to be updated on the Broadcast Sink 3340 */ 3341 public void modifySource( 3342 BluetoothDevice sink, int sourceId, BluetoothLeBroadcastMetadata updatedMetadata) { 3343 log( 3344 "modifySource: " 3345 + ("device: " + sink) 3346 + ("sourceId: " + sourceId) 3347 + (", updatedMetadata: " + updatedMetadata)); 3348 3349 Map<BluetoothDevice, Integer> devices = getGroupManagedDeviceSources(sink, sourceId).second; 3350 3351 for (Map.Entry<BluetoothDevice, Integer> deviceSourceIdPair : devices.entrySet()) { 3352 BluetoothDevice device = deviceSourceIdPair.getKey(); 3353 Integer deviceSourceId = deviceSourceIdPair.getValue(); 3354 3355 if (updatedMetadata == null) { 3356 log("modifySource: Error bad parameters: updatedMetadata cannot be null"); 3357 mCallbacks.notifySourceModifyFailed( 3358 device, deviceSourceId, BluetoothStatusCodes.ERROR_BAD_PARAMETERS); 3359 continue; 3360 } 3361 3362 BassClientStateMachine stateMachine = getOrCreateStateMachine(device); 3363 int statusCode = 3364 validateParametersForSourceOperation( 3365 stateMachine, device, updatedMetadata, deviceSourceId); 3366 if (statusCode != BluetoothStatusCodes.SUCCESS) { 3367 mCallbacks.notifySourceModifyFailed(device, deviceSourceId, statusCode); 3368 continue; 3369 } 3370 if (stateMachine.hasPendingSourceOperation()) { 3371 Log.w( 3372 TAG, 3373 "modifySource: source operation already pending, device: " 3374 + device 3375 + ", broadcastId: " 3376 + updatedMetadata.getBroadcastId()); 3377 mCallbacks.notifySourceModifyFailed( 3378 device, deviceSourceId, BluetoothStatusCodes.ERROR_ALREADY_IN_TARGET_STATE); 3379 continue; 3380 } 3381 3382 /* Update metadata for sink device */ 3383 storeSinkMetadata(device, updatedMetadata.getBroadcastId(), updatedMetadata); 3384 3385 sEventLogger.logd( 3386 TAG, 3387 "Modify Broadcast Source: " 3388 + ("device: " + device) 3389 + ("sourceId: " + deviceSourceId) 3390 + (", updatedBroadcastId: " + updatedMetadata.getBroadcastId()) 3391 + (", updatedBroadcastName: " + updatedMetadata.getBroadcastName())); 3392 3393 Message message = 3394 stateMachine.obtainMessage(BassClientStateMachine.UPDATE_BCAST_SOURCE); 3395 message.arg1 = deviceSourceId; 3396 message.arg2 = BassConstants.INVALID_PA_SYNC_VALUE; 3397 message.obj = updatedMetadata; 3398 stateMachine.sendMessage(message); 3399 3400 byte[] code = updatedMetadata.getBroadcastCode(); 3401 if (code != null && code.length != 0) { 3402 sEventLogger.logd( 3403 TAG, 3404 "Set Broadcast Code (Modify Source context): " 3405 + ("device: " + device) 3406 + ("sourceId: " + deviceSourceId) 3407 + (", updatedBroadcastId: " + updatedMetadata.getBroadcastId()) 3408 + (", updatedBroadcastName: " 3409 + updatedMetadata.getBroadcastName())); 3410 message = stateMachine.obtainMessage(BassClientStateMachine.SET_BCAST_CODE); 3411 message.obj = updatedMetadata; 3412 message.arg1 = BassClientStateMachine.ARGTYPE_METADATA; 3413 stateMachine.sendMessage(message); 3414 } 3415 } 3416 } 3417 3418 /** 3419 * A public method for removing a Broadcast Source from a Broadcast Sink. It also supports group 3420 * removal if addSource was previously used with a group. Designed for external use, this method 3421 * always removes sources along with their cached values, even if they were suspended, as this 3422 * is intended by the user. 3423 * 3424 * @param sink representing the Broadcast Sink from which a Broadcast Source should be removed 3425 * @param sourceId source ID as delivered in onSourceAdded 3426 */ 3427 public void removeSource(BluetoothDevice sink, int sourceId) { 3428 log("removeSource: device: " + sink + ", sourceId: " + sourceId); 3429 3430 Map<BluetoothDevice, Integer> devices = getGroupManagedDeviceSources(sink, sourceId).second; 3431 for (Map.Entry<BluetoothDevice, Integer> deviceSourceIdPair : devices.entrySet()) { 3432 BluetoothDevice device = deviceSourceIdPair.getKey(); 3433 Integer deviceSourceId = deviceSourceIdPair.getValue(); 3434 3435 mPausedBroadcastSinks.remove(device); 3436 3437 BassClientStateMachine stateMachine = getOrCreateStateMachine(device); 3438 int statusCode = 3439 validateParametersForSourceOperation(stateMachine, device, deviceSourceId); 3440 if (statusCode != BluetoothStatusCodes.SUCCESS) { 3441 removeSinkMetadata(device); 3442 mCallbacks.notifySourceRemoveFailed(device, deviceSourceId, statusCode); 3443 continue; 3444 } 3445 3446 BluetoothLeBroadcastMetadata metaData = 3447 stateMachine.getCurrentBroadcastMetadata(deviceSourceId); 3448 if (metaData != null) { 3449 removeSinkMetadata(device, metaData.getBroadcastId()); 3450 } else { 3451 removeSinkMetadata(device); 3452 } 3453 3454 removeSourceInternal(device, deviceSourceId, stateMachine, metaData); 3455 } 3456 } 3457 3458 /** 3459 * Removes the Broadcast Source from a single Broadcast Sink 3460 * 3461 * @param sink representing the Broadcast Sink from which a Broadcast Source should be removed 3462 * @param sourceId source ID as delivered in onSourceAdded 3463 * @param stateMachine stateMachine for this sink 3464 * @param metaData current broadcast metadata for this sink 3465 */ 3466 private void removeSourceInternal( 3467 BluetoothDevice sink, 3468 int sourceId, 3469 BassClientStateMachine stateMachine, 3470 BluetoothLeBroadcastMetadata metaData) { 3471 log("removeSourceInternal: device: " + sink + ", sourceId: " + sourceId); 3472 if (metaData != null) { 3473 stopBigMonitoring(metaData.getBroadcastId(), /* hostInitiated */ true); 3474 } 3475 3476 if (stateMachine.isSyncedToTheSource(sourceId)) { 3477 sEventLogger.logd( 3478 TAG, 3479 "Remove Broadcast Source(Force lost PA sync): " 3480 + ("device: " + sink) 3481 + (", sourceId: " + sourceId) 3482 + (", broadcastId: " 3483 + ((metaData == null) 3484 ? BassConstants.INVALID_BROADCAST_ID 3485 : metaData.getBroadcastId())) 3486 + (", broadcastName: " 3487 + ((metaData == null) ? "" : metaData.getBroadcastName()))); 3488 3489 log("Force source to lost PA sync"); 3490 Message message = 3491 stateMachine.obtainMessage(BassClientStateMachine.UPDATE_BCAST_SOURCE); 3492 message.arg1 = sourceId; 3493 message.arg2 = BassConstants.PA_SYNC_DO_NOT_SYNC; 3494 /* Pending remove set. Remove source once not synchronized to PA */ 3495 /* MetaData can be null if source is from remote's receive state */ 3496 message.obj = metaData; 3497 stateMachine.sendMessage(message); 3498 } else { 3499 sEventLogger.logd( 3500 TAG, "Remove Broadcast Source: device: " + sink + ", sourceId: " + sourceId); 3501 3502 Message message = 3503 stateMachine.obtainMessage(BassClientStateMachine.REMOVE_BCAST_SOURCE); 3504 message.arg1 = sourceId; 3505 stateMachine.sendMessage(message); 3506 } 3507 3508 enqueueSourceGroupOp( 3509 sink, BassClientStateMachine.REMOVE_BCAST_SOURCE, Integer.valueOf(sourceId)); 3510 } 3511 3512 /** 3513 * Get information about all Broadcast Sources 3514 * 3515 * @param sink Broadcast Sink from which to get all Broadcast Sources 3516 * @return the list of Broadcast Receive State {@link BluetoothLeBroadcastReceiveState} 3517 */ 3518 public List<BluetoothLeBroadcastReceiveState> getAllSources(BluetoothDevice sink) { 3519 synchronized (mStateMachines) { 3520 BassClientStateMachine stateMachine = getOrCreateStateMachine(sink); 3521 if (stateMachine == null) { 3522 log("stateMachine is null"); 3523 return Collections.emptyList(); 3524 } 3525 return stateMachine.getAllSources().stream() 3526 .filter(rs -> !isEmptyBluetoothDevice(rs.getSourceDevice())) 3527 .collect(Collectors.toList()); 3528 } 3529 } 3530 3531 /** 3532 * Get maximum number of sources that can be added to this Broadcast Sink 3533 * 3534 * @param sink Broadcast Sink device 3535 * @return maximum number of sources that can be added to this Broadcast Sink 3536 */ 3537 int getMaximumSourceCapacity(BluetoothDevice sink) { 3538 log("getMaximumSourceCapacity: device = " + sink); 3539 BassClientStateMachine stateMachine = getOrCreateStateMachine(sink); 3540 if (stateMachine == null) { 3541 log("stateMachine is null"); 3542 return 0; 3543 } 3544 return stateMachine.getMaximumSourceCapacity(); 3545 } 3546 3547 /** 3548 * Get metadata of source that stored on this Broadcast Sink 3549 * 3550 * @param sink Broadcast Sink device 3551 * @param sourceId Broadcast source id 3552 * @return metadata of source that stored on this Broadcast Sink 3553 */ 3554 BluetoothLeBroadcastMetadata getSourceMetadata(BluetoothDevice sink, int sourceId) { 3555 if (!leaudioBroadcastApiGetLocalMetadata()) { 3556 return null; 3557 } 3558 3559 log("getSourceMetadata: device = " + sink + " with source id = " + sourceId); 3560 BassClientStateMachine stateMachine = getOrCreateStateMachine(sink); 3561 if (stateMachine == null) { 3562 log("stateMachine is null"); 3563 return null; 3564 } 3565 return stateMachine.getCurrentBroadcastMetadata(sourceId); 3566 } 3567 3568 private boolean isLocalBroadcast(int broadcastId) { 3569 LeAudioService leAudioService = mServiceFactory.getLeAudioService(); 3570 if (leAudioService == null) { 3571 return false; 3572 } 3573 3574 boolean wasFound = 3575 leAudioService.getAllBroadcastMetadata().stream() 3576 .anyMatch( 3577 meta -> { 3578 return meta.getBroadcastId() == broadcastId; 3579 }); 3580 log("isLocalBroadcast=" + wasFound); 3581 return wasFound; 3582 } 3583 3584 boolean isLocalBroadcast(BluetoothLeBroadcastMetadata metaData) { 3585 if (metaData == null) { 3586 return false; 3587 } 3588 3589 return isLocalBroadcast(metaData.getBroadcastId()); 3590 } 3591 3592 boolean isLocalBroadcast(BluetoothLeBroadcastReceiveState receiveState) { 3593 if (receiveState == null) { 3594 return false; 3595 } 3596 3597 return isLocalBroadcast(receiveState.getBroadcastId()); 3598 } 3599 3600 static void log(String msg) { 3601 Log.d(TAG, msg); 3602 } 3603 3604 private List<Pair<BluetoothLeBroadcastReceiveState, BluetoothDevice>> 3605 getReceiveStateDevicePairs(int broadcastId) { 3606 List<Pair<BluetoothLeBroadcastReceiveState, BluetoothDevice>> list = new ArrayList<>(); 3607 3608 for (BluetoothDevice device : getConnectedDevices()) { 3609 for (BluetoothLeBroadcastReceiveState receiveState : getAllSources(device)) { 3610 /* Check if local/last broadcast is the synced one. Invalid broadcast ID means 3611 * that all receivers should be considered. 3612 */ 3613 if ((broadcastId != BassConstants.INVALID_BROADCAST_ID) 3614 && (receiveState.getBroadcastId() != broadcastId)) { 3615 continue; 3616 } 3617 3618 list.add( 3619 new Pair<BluetoothLeBroadcastReceiveState, BluetoothDevice>( 3620 receiveState, device)); 3621 } 3622 } 3623 3624 return list; 3625 } 3626 3627 private void cancelPendingSourceOperations(int broadcastId) { 3628 for (BluetoothDevice device : getConnectedDevices()) { 3629 synchronized (mStateMachines) { 3630 BassClientStateMachine sm = getOrCreateStateMachine(device); 3631 if (sm != null && sm.hasPendingSourceOperation(broadcastId)) { 3632 Message message = 3633 sm.obtainMessage( 3634 BassClientStateMachine.CANCEL_PENDING_SOURCE_OPERATION); 3635 message.arg1 = broadcastId; 3636 sm.sendMessage(message); 3637 } 3638 } 3639 } 3640 } 3641 3642 private void stopSourceReceivers(int broadcastId) { 3643 log("stopSourceReceivers broadcastId: " + broadcastId); 3644 3645 List<Pair<BluetoothLeBroadcastReceiveState, BluetoothDevice>> sourcesToRemove = 3646 getReceiveStateDevicePairs(broadcastId); 3647 3648 for (Pair<BluetoothLeBroadcastReceiveState, BluetoothDevice> pair : sourcesToRemove) { 3649 removeSource(pair.second, pair.first.getSourceId()); 3650 } 3651 3652 if (!leaudioBroadcastResyncHelper() || broadcastId != BassConstants.INVALID_BROADCAST_ID) { 3653 /* There may be some pending add/modify source operations */ 3654 cancelPendingSourceOperations(broadcastId); 3655 } 3656 } 3657 3658 /** 3659 * Suspends source receivers for the given broadcast ID 3660 * 3661 * @param broadcastId The broadcast ID for which the receivers should be stopped or suspended 3662 */ 3663 private void suspendSourceReceivers(int broadcastId) { 3664 log("suspendSourceReceivers broadcastId: " + broadcastId); 3665 3666 List<Pair<BluetoothDevice, Integer>> sourcesToModify = new ArrayList<>(); 3667 HashSet<Integer> broadcastIdsToStopMonitoring = new HashSet<>(); 3668 for (BluetoothDevice device : getConnectedDevices()) { 3669 if (!leaudioBroadcastResyncHelper()) { 3670 if (mPausedBroadcastSinks.contains(device)) { 3671 // Skip this device if it has been paused 3672 continue; 3673 } 3674 3675 for (BluetoothLeBroadcastReceiveState receiveState : getAllSources(device)) { 3676 /* Check if local/last broadcast is the synced one. Invalid broadcast ID means 3677 * that all receivers should be considered. 3678 */ 3679 if ((broadcastId != BassConstants.INVALID_BROADCAST_ID) 3680 && (receiveState.getBroadcastId() != broadcastId)) { 3681 continue; 3682 } 3683 3684 sEventLogger.logd(TAG, "Add broadcast sink to paused cache: " + device); 3685 mPausedBroadcastSinks.add(device); 3686 3687 sourcesToModify.add( 3688 new Pair<BluetoothDevice, Integer>(device, receiveState.getSourceId())); 3689 } 3690 } else { 3691 for (BluetoothLeBroadcastReceiveState receiveState : getAllSources(device)) { 3692 /* Check if local/last broadcast is the synced one. Invalid broadcast ID means 3693 * that all receivers should be considered. 3694 */ 3695 if ((broadcastId != BassConstants.INVALID_BROADCAST_ID) 3696 && (receiveState.getBroadcastId() != broadcastId)) { 3697 continue; 3698 } 3699 3700 broadcastIdsToStopMonitoring.add(receiveState.getBroadcastId()); 3701 3702 sourcesToModify.add( 3703 new Pair<BluetoothDevice, Integer>(device, receiveState.getSourceId())); 3704 3705 sEventLogger.logd(TAG, "Add broadcast sink to paused cache: " + device); 3706 mPausedBroadcastSinks.add(device); 3707 } 3708 } 3709 } 3710 3711 for (int broadcastIdToStopMonitoring : broadcastIdsToStopMonitoring) { 3712 stopBigMonitoring(broadcastIdToStopMonitoring, /* hostInitiated */ true); 3713 } 3714 3715 /* Suspend all previously marked sources with modify source operation */ 3716 for (Pair<BluetoothDevice, Integer> pair : sourcesToModify) { 3717 BluetoothDevice device = pair.first; 3718 3719 BassClientStateMachine sm = mStateMachines.get(device); 3720 if (sm == null) { 3721 Log.e( 3722 TAG, 3723 "suspendSourceReceivers: invalid state machine for device: " + pair.first); 3724 continue; 3725 } 3726 3727 int sourceId = pair.second; 3728 int paSyncValue = BassConstants.PA_SYNC_DO_NOT_SYNC; 3729 BluetoothLeBroadcastMetadata metadata = sm.getCurrentBroadcastMetadata(sourceId); 3730 3731 sEventLogger.logd( 3732 TAG, 3733 "Modify Broadcast Source: " 3734 + ("device: " + device) 3735 + (", sourceId: " + sourceId) 3736 + (", PA sync value: " + paSyncValue) 3737 + (", updatedBroadcastId: " + metadata.getBroadcastId()) 3738 + (", updatedBroadcastName: " + metadata.getBroadcastName())); 3739 Message message = sm.obtainMessage(BassClientStateMachine.UPDATE_BCAST_SOURCE); 3740 message.arg1 = sourceId; 3741 message.arg2 = paSyncValue; 3742 message.obj = metadata; 3743 sm.sendMessage(message); 3744 } 3745 3746 if (leaudioBroadcastResyncHelper()) { 3747 if (broadcastId != BassConstants.INVALID_BROADCAST_ID) { 3748 /* There may be some pending add/modify source operations */ 3749 cancelPendingSourceOperations(broadcastId); 3750 } 3751 } 3752 } 3753 3754 /** Return true if there is any non primary device receiving broadcast */ 3755 private boolean isAudioSharingModeOn(Integer broadcastId) { 3756 HashSet<BluetoothDevice> devices = mLocalBroadcastReceivers.get(broadcastId); 3757 if (devices == null) { 3758 Log.w(TAG, "isAudioSharingModeOn: No receivers receiving broadcast: " + broadcastId); 3759 return false; 3760 } 3761 3762 LeAudioService leAudioService = mServiceFactory.getLeAudioService(); 3763 if (leAudioService == null) { 3764 Log.d(TAG, "isAudioSharingModeOn: No available LeAudioService"); 3765 return false; 3766 } 3767 3768 return devices.stream().anyMatch(d -> !leAudioService.isPrimaryDevice(d)); 3769 } 3770 3771 /** Handle disconnection of potential broadcast sinks */ 3772 public void handleDeviceDisconnection(BluetoothDevice sink, boolean isIntentional) { 3773 LeAudioService leAudioService = mServiceFactory.getLeAudioService(); 3774 if (leAudioService == null) { 3775 Log.d(TAG, "BluetoothLeBroadcastReceiveState: No available LeAudioService"); 3776 return; 3777 } 3778 3779 Iterator<Map.Entry<Integer, HashSet<BluetoothDevice>>> iterator = 3780 mLocalBroadcastReceivers.entrySet().iterator(); 3781 while (iterator.hasNext()) { 3782 Map.Entry<Integer, HashSet<BluetoothDevice>> entry = iterator.next(); 3783 Integer broadcastId = entry.getKey(); 3784 HashSet<BluetoothDevice> devices = entry.getValue(); 3785 3786 if (leaudioBigDependsOnAudioState()) { 3787 /* If somehow there is a non configured/playing broadcast, let's remove it */ 3788 if (!(leAudioService.isPaused(broadcastId) 3789 || leAudioService.isPlaying(broadcastId))) { 3790 Log.w(TAG, "Non playing broadcast remove from receivers list"); 3791 iterator.remove(); 3792 continue; 3793 } 3794 } else { 3795 /* If somehow there is a non playing broadcast, let's remove it */ 3796 if (!leAudioService.isPlaying(broadcastId)) { 3797 Log.w(TAG, "Non playing broadcast remove from receivers list"); 3798 iterator.remove(); 3799 continue; 3800 } 3801 } 3802 3803 if (isIntentional) { 3804 /* Check if disconnecting device participated in this broadcast reception */ 3805 if (!devices.remove(sink)) { 3806 continue; 3807 } 3808 3809 removeSinkMetadata(sink); 3810 3811 /* Check if there is any other primary device receiving this broadcast */ 3812 if (devices.stream() 3813 .anyMatch( 3814 d -> 3815 ((getConnectionState(d) == STATE_CONNECTED) 3816 && leAudioService.isPrimaryDevice(d)))) { 3817 continue; 3818 } 3819 3820 Log.d( 3821 TAG, 3822 "handleIntendedDeviceDisconnection: No more potential broadcast " 3823 + "(broadcast ID: " 3824 + broadcastId 3825 + ") receivers - stopping broadcast"); 3826 iterator.remove(); 3827 leAudioService.stopBroadcast(broadcastId); 3828 } else { 3829 /* Unintentional disconnection of primary device in private broadcast mode */ 3830 if (!isAudioSharingModeOn(broadcastId) 3831 && !devices.stream() 3832 .anyMatch( 3833 d -> 3834 !d.equals(sink) 3835 && (getConnectionState(d) 3836 == STATE_CONNECTED))) { 3837 iterator.remove(); 3838 leAudioService.stopBroadcast(broadcastId); 3839 continue; 3840 } 3841 3842 /* Unintentional disconnection of primary/secondary in broadcast sharing mode */ 3843 if (devices.stream() 3844 .anyMatch( 3845 d -> 3846 !d.equals(sink) 3847 && (getConnectionState(d) == STATE_CONNECTED))) { 3848 continue; 3849 } 3850 Log.d( 3851 TAG, 3852 "handleUnintendedDeviceDisconnection: No more potential broadcast " 3853 + "(broadcast ID: " 3854 + broadcastId 3855 + ") receivers - stopping broadcast"); 3856 mDialingOutTimeoutEvent = new DialingOutTimeoutEvent(broadcastId); 3857 mHandler.postDelayed(mDialingOutTimeoutEvent, DIALING_OUT_TIMEOUT_MS); 3858 } 3859 } 3860 } 3861 3862 /* Handle device Bass state ready and check if assistant should resume broadcast */ 3863 private void handleBassStateReady(BluetoothDevice sink) { 3864 // Check its peer device still has active source 3865 Map<Integer, BluetoothLeBroadcastMetadata> entry = mBroadcastMetadataMap.get(sink); 3866 3867 if (entry != null) { 3868 for (Map.Entry<Integer, BluetoothLeBroadcastMetadata> idMetadataIdPair : 3869 entry.entrySet()) { 3870 BluetoothLeBroadcastMetadata metadata = idMetadataIdPair.getValue(); 3871 if (metadata == null) { 3872 Log.d(TAG, "handleBassStateReady: no metadata available"); 3873 continue; 3874 } 3875 for (BluetoothDevice groupDevice : 3876 getTargetDeviceList(sink, /* isGroupOp */ true)) { 3877 if (groupDevice.equals(sink)) { 3878 continue; 3879 } 3880 // Check peer device 3881 Optional<BluetoothLeBroadcastReceiveState> receiver = 3882 getOrCreateStateMachine(groupDevice).getAllSources().stream() 3883 .filter(e -> e.getBroadcastId() == metadata.getBroadcastId()) 3884 .findAny(); 3885 if (receiver.isPresent() 3886 && !getAllSources(sink).stream() 3887 .anyMatch( 3888 rs -> 3889 (rs.getBroadcastId() 3890 == receiver.get().getBroadcastId()))) { 3891 Log.d(TAG, "handleBassStateReady: restore the source for device, " + sink); 3892 addSource(sink, metadata, /* isGroupOp */ false); 3893 return; 3894 } 3895 } 3896 } 3897 } else { 3898 Log.d(TAG, "handleBassStateReady: no entry for device: " + sink + ", available"); 3899 } 3900 3901 // Continue to check if there is pending source to add due to BASS not ready 3902 synchronized (mPendingSourcesToAdd) { 3903 Iterator<AddSourceData> iterator = mPendingSourcesToAdd.iterator(); 3904 while (iterator.hasNext()) { 3905 AddSourceData pendingSourcesToAdd = iterator.next(); 3906 if (pendingSourcesToAdd.sink.equals(sink)) { 3907 Log.d(TAG, "handleBassStateReady: retry adding source with device, " + sink); 3908 addSource(pendingSourcesToAdd.sink, pendingSourcesToAdd.sourceMetadata, false); 3909 iterator.remove(); 3910 return; 3911 } 3912 } 3913 } 3914 } 3915 3916 /* Handle device Bass state setup failed */ 3917 private void handleBassStateSetupFailed(BluetoothDevice sink) { 3918 // Check if there is pending source to add due to BASS not ready 3919 synchronized (mPendingSourcesToAdd) { 3920 Iterator<AddSourceData> iterator = mPendingSourcesToAdd.iterator(); 3921 while (iterator.hasNext()) { 3922 AddSourceData pendingSourcesToAdd = iterator.next(); 3923 if (pendingSourcesToAdd.sink.equals(sink)) { 3924 mCallbacks.notifySourceAddFailed( 3925 pendingSourcesToAdd.sink, 3926 pendingSourcesToAdd.sourceMetadata, 3927 BluetoothStatusCodes.ERROR_REMOTE_NOT_ENOUGH_RESOURCES); 3928 iterator.remove(); 3929 return; 3930 } 3931 } 3932 } 3933 } 3934 3935 private void logPausedBroadcastsAndSinks() { 3936 log( 3937 "mPausedBroadcastIds: " 3938 + mPausedBroadcastIds 3939 + ", mPausedBroadcastSinks: " 3940 + mPausedBroadcastSinks); 3941 } 3942 3943 private boolean isHostPauseType(int broadcastId) { 3944 return (mPausedBroadcastIds.containsKey(broadcastId) 3945 && mPausedBroadcastIds.get(broadcastId).equals(PauseType.HOST_INTENTIONAL)); 3946 } 3947 3948 private boolean isSinkUnintentionalPauseType(int broadcastId) { 3949 return (mPausedBroadcastIds.containsKey(broadcastId) 3950 && mPausedBroadcastIds.get(broadcastId).equals(PauseType.SINK_UNINTENTIONAL)); 3951 } 3952 3953 public void stopBigMonitoring() { 3954 if (!leaudioBroadcastResyncHelper()) { 3955 return; 3956 } 3957 log("stopBigMonitoring"); 3958 mPausedBroadcastSinks.clear(); 3959 3960 Iterator<Integer> iterator = mPausedBroadcastIds.keySet().iterator(); 3961 while (iterator.hasNext()) { 3962 int pausedBroadcastId = iterator.next(); 3963 mTimeoutHandler.stop(pausedBroadcastId, MESSAGE_BIG_MONITOR_TIMEOUT); 3964 mTimeoutHandler.stop(pausedBroadcastId, MESSAGE_BROADCAST_MONITOR_TIMEOUT); 3965 iterator.remove(); 3966 synchronized (mSearchScanCallbackLock) { 3967 // when searching is stopped then stop active sync 3968 if (!isSearchInProgress()) { 3969 cancelActiveSync(getSyncHandleForBroadcastId(pausedBroadcastId)); 3970 } 3971 } 3972 } 3973 logPausedBroadcastsAndSinks(); 3974 } 3975 3976 private void checkAndStopBigMonitoring() { 3977 if (!leaudioBroadcastResyncHelper()) { 3978 return; 3979 } 3980 log("checkAndStopBigMonitoring"); 3981 Iterator<Integer> iterator = mPausedBroadcastIds.keySet().iterator(); 3982 while (iterator.hasNext()) { 3983 int pausedBroadcastId = iterator.next(); 3984 if (!isAnyReceiverSyncedToBroadcast(pausedBroadcastId)) { 3985 mTimeoutHandler.stop(pausedBroadcastId, MESSAGE_BIG_MONITOR_TIMEOUT); 3986 mTimeoutHandler.stop(pausedBroadcastId, MESSAGE_BROADCAST_MONITOR_TIMEOUT); 3987 3988 if (isSinkUnintentionalPauseType(pausedBroadcastId) 3989 || (isHostPauseType(pausedBroadcastId) 3990 && mPausedBroadcastSinks.isEmpty())) { 3991 iterator.remove(); 3992 } 3993 synchronized (mSearchScanCallbackLock) { 3994 // when searching is stopped then stop active sync 3995 if (!isSearchInProgress()) { 3996 cancelActiveSync(getSyncHandleForBroadcastId(pausedBroadcastId)); 3997 } 3998 } 3999 logPausedBroadcastsAndSinks(); 4000 } 4001 } 4002 } 4003 4004 private void stopBigMonitoring(int broadcastId, boolean hostInitiated) { 4005 if (!leaudioBroadcastResyncHelper()) { 4006 return; 4007 } 4008 log("stopBigMonitoring broadcastId: " + broadcastId + ", hostInitiated: " + hostInitiated); 4009 mTimeoutHandler.stop(broadcastId, MESSAGE_BIG_MONITOR_TIMEOUT); 4010 mTimeoutHandler.stop(broadcastId, MESSAGE_BROADCAST_MONITOR_TIMEOUT); 4011 if (hostInitiated) { 4012 mPausedBroadcastIds.put(broadcastId, PauseType.HOST_INTENTIONAL); 4013 } else { 4014 mPausedBroadcastIds.remove(broadcastId); 4015 mPausedBroadcastSinks.clear(); 4016 } 4017 stopActiveSync(broadcastId); 4018 logPausedBroadcastsAndSinks(); 4019 } 4020 4021 private void stopActiveSync(int broadcastId) { 4022 synchronized (mSearchScanCallbackLock) { 4023 // when searching is stopped then stop active sync 4024 if (!isSearchInProgress()) { 4025 if (leaudioMonitorUnicastSourceWhenManagedByBroadcastDelegator()) { 4026 boolean waitingForPast = false; 4027 synchronized (mSinksWaitingForPast) { 4028 waitingForPast = 4029 mSinksWaitingForPast.entrySet().stream() 4030 .anyMatch(entry -> entry.getValue().first == broadcastId); 4031 } 4032 if (!waitingForPast) { 4033 cancelActiveSync(getSyncHandleForBroadcastId(broadcastId)); 4034 } 4035 } else { 4036 cancelActiveSync(getSyncHandleForBroadcastId(broadcastId)); 4037 } 4038 } 4039 } 4040 } 4041 4042 /** Cache suspending sources when broadcast paused */ 4043 public void cacheSuspendingSources(int broadcastId) { 4044 sEventLogger.logd(TAG, "Cache suspending sources: " + broadcastId); 4045 List<Pair<BluetoothLeBroadcastReceiveState, BluetoothDevice>> sourcesToCache = 4046 getReceiveStateDevicePairs(broadcastId); 4047 4048 for (Pair<BluetoothLeBroadcastReceiveState, BluetoothDevice> pair : sourcesToCache) { 4049 mPausedBroadcastSinks.add(pair.second); 4050 } 4051 4052 logPausedBroadcastsAndSinks(); 4053 } 4054 4055 /** Request receivers to suspend broadcast sources synchronization */ 4056 @VisibleForTesting 4057 void suspendReceiversSourceSynchronization(int broadcastId) { 4058 sEventLogger.logd(TAG, "Suspend receivers source synchronization: " + broadcastId); 4059 suspendSourceReceivers(broadcastId); 4060 } 4061 4062 /** Request all receivers to suspend broadcast sources synchronization */ 4063 @VisibleForTesting 4064 void suspendAllReceiversSourceSynchronization() { 4065 sEventLogger.logd(TAG, "Suspend all receivers source synchronization"); 4066 suspendSourceReceivers(BassConstants.INVALID_BROADCAST_ID); 4067 } 4068 4069 /** Request receivers to stop broadcast sources synchronization and remove them */ 4070 public void stopReceiversSourceSynchronization(int broadcastId) { 4071 sEventLogger.logd(TAG, "Stop receivers source synchronization: " + broadcastId); 4072 stopSourceReceivers(broadcastId); 4073 } 4074 4075 /** Request receivers to resume broadcast source synchronization */ 4076 public void resumeReceiversSourceSynchronization() { 4077 sEventLogger.logd(TAG, "Resume receivers source synchronization"); 4078 4079 Iterator<BluetoothDevice> iterator = mPausedBroadcastSinks.iterator(); 4080 while (iterator.hasNext()) { 4081 BluetoothDevice sink = iterator.next(); 4082 sEventLogger.logd(TAG, "Remove broadcast sink from paused cache: " + sink); 4083 Map<Integer, BluetoothLeBroadcastMetadata> entry = 4084 mBroadcastMetadataMap.getOrDefault(sink, Collections.emptyMap()); 4085 4086 for (BluetoothLeBroadcastMetadata metadata : entry.values()) { 4087 4088 if (leaudioBroadcastResyncHelper()) { 4089 if (metadata == null) { 4090 Log.w( 4091 TAG, 4092 "resumeReceiversSourceSynchronization: failed to get metadata to" 4093 + " resume sink: " 4094 + sink); 4095 continue; 4096 } 4097 4098 mPausedBroadcastIds.remove(metadata.getBroadcastId()); 4099 4100 // For each device, find the source ID having this broadcast ID 4101 BassClientStateMachine stateMachine = getOrCreateStateMachine(sink); 4102 List<BluetoothLeBroadcastReceiveState> sources = stateMachine.getAllSources(); 4103 Optional<BluetoothLeBroadcastReceiveState> receiveState = 4104 sources.stream() 4105 .filter(e -> e.getBroadcastId() == metadata.getBroadcastId()) 4106 .findAny(); 4107 4108 if (leaudioBroadcastResyncHelper() 4109 && receiveState.isPresent() 4110 && (receiveState.get().getPaSyncState() 4111 == BluetoothLeBroadcastReceiveState 4112 .PA_SYNC_STATE_SYNCINFO_REQUEST 4113 || receiveState.get().getPaSyncState() 4114 == BluetoothLeBroadcastReceiveState 4115 .PA_SYNC_STATE_SYNCHRONIZED)) { 4116 continue; 4117 } 4118 4119 if (receiveState.isPresent() 4120 && (!leaudioBroadcastResyncHelper() 4121 || isLocalBroadcast(metadata) 4122 || getActiveSyncedSources() 4123 .contains( 4124 getSyncHandleForBroadcastId( 4125 metadata.getBroadcastId())))) { 4126 int sourceId = receiveState.get().getSourceId(); 4127 updateSourceToResumeBroadcast(sink, sourceId, metadata); 4128 } else { 4129 addSource(sink, metadata, /* isGroupOp */ false); 4130 } 4131 } else { 4132 if (metadata != null) { 4133 mPausedBroadcastIds.remove(metadata.getBroadcastId()); 4134 addSource(sink, metadata, /* isGroupOp */ false); 4135 } else { 4136 Log.w( 4137 TAG, 4138 "resumeReceiversSourceSynchronization: failed to get metadata to" 4139 + " resume sink: " 4140 + sink); 4141 } 4142 } 4143 } 4144 // remove the device from mPausedBroadcastSinks 4145 iterator.remove(); 4146 } 4147 4148 logPausedBroadcastsAndSinks(); 4149 } 4150 4151 private void updateSourceToResumeBroadcast( 4152 BluetoothDevice sink, int sourceId, BluetoothLeBroadcastMetadata metadata) { 4153 BassClientStateMachine stateMachine = getOrCreateStateMachine(sink); 4154 int statusCode = 4155 validateParametersForSourceOperation(stateMachine, sink, metadata, sourceId); 4156 if (statusCode != BluetoothStatusCodes.SUCCESS) { 4157 return; 4158 } 4159 if (stateMachine.hasPendingSourceOperation()) { 4160 Log.w( 4161 TAG, 4162 "updateSourceToResumeBroadcast: source operation already pending, device: " 4163 + sink 4164 + ", broadcastId: " 4165 + metadata.getBroadcastId()); 4166 return; 4167 } 4168 4169 sEventLogger.logd( 4170 TAG, 4171 "Modify Broadcast Source (resume): " 4172 + ("device: " + sink) 4173 + (", sourceId: " + sourceId) 4174 + (", updatedBroadcastId: " + metadata.getBroadcastId()) 4175 + (", updatedBroadcastName: " + metadata.getBroadcastName())); 4176 Message message = stateMachine.obtainMessage(BassClientStateMachine.UPDATE_BCAST_SOURCE); 4177 message.arg1 = sourceId; 4178 message.arg2 = 4179 DeviceConfig.getBoolean( 4180 DeviceConfig.NAMESPACE_BLUETOOTH, 4181 "persist.vendor.service.bt.defNoPAS", 4182 false) 4183 ? BassConstants.PA_SYNC_PAST_NOT_AVAILABLE 4184 : BassConstants.PA_SYNC_PAST_AVAILABLE; 4185 message.obj = metadata; 4186 stateMachine.sendMessage(message); 4187 } 4188 4189 /** Handle Unicast source stream status change */ 4190 public void handleUnicastSourceStreamStatusChange(int status) { 4191 mUnicastSourceStreamStatus = Optional.of(status); 4192 4193 if (status == STATUS_LOCAL_STREAM_REQUESTED) { 4194 if ((leaudioMonitorUnicastSourceWhenManagedByBroadcastDelegator() 4195 && hasPrimaryDeviceManagedExternalBroadcast()) 4196 || (!leaudioMonitorUnicastSourceWhenManagedByBroadcastDelegator() 4197 && areReceiversReceivingOnlyExternalBroadcast(getConnectedDevices()))) { 4198 cacheSuspendingSources(BassConstants.INVALID_BROADCAST_ID); 4199 List<Pair<BluetoothLeBroadcastReceiveState, BluetoothDevice>> sourcesToStop = 4200 getReceiveStateDevicePairs(BassConstants.INVALID_BROADCAST_ID); 4201 for (Pair<BluetoothLeBroadcastReceiveState, BluetoothDevice> pair : sourcesToStop) { 4202 stopBigMonitoring(pair.first.getBroadcastId(), /* hostInitiated */ true); 4203 } 4204 } 4205 if (!leaudioMonitorUnicastSourceWhenManagedByBroadcastDelegator()) { 4206 for (Map.Entry<Integer, PauseType> entry : mPausedBroadcastIds.entrySet()) { 4207 Integer broadcastId = entry.getKey(); 4208 PauseType pauseType = entry.getValue(); 4209 if (pauseType != PauseType.HOST_INTENTIONAL) { 4210 suspendReceiversSourceSynchronization(broadcastId); 4211 } 4212 } 4213 } 4214 } else if (status == STATUS_LOCAL_STREAM_SUSPENDED) { 4215 /* Resume paused receivers if there are some */ 4216 if (!mPausedBroadcastSinks.isEmpty()) { 4217 resumeReceiversSourceSynchronization(); 4218 } 4219 } else if (status == STATUS_LOCAL_STREAM_STREAMING) { 4220 Log.d(TAG, "Ignore STREAMING source status"); 4221 } else if (status == STATUS_LOCAL_STREAM_REQUESTED_NO_CONTEXT_VALIDATE) { 4222 suspendAllReceiversSourceSynchronization(); 4223 } 4224 } 4225 4226 /** Check if any sink receivers are receiving broadcast stream */ 4227 public boolean isAnyReceiverActive(List<BluetoothDevice> devices) { 4228 for (BluetoothDevice device : devices) { 4229 for (BluetoothLeBroadcastReceiveState receiveState : getAllSources(device)) { 4230 if (isReceiverActive(receiveState)) { 4231 return true; 4232 } 4233 } 4234 } 4235 4236 return false; 4237 } 4238 4239 public boolean hasPrimaryDeviceManagedExternalBroadcast() { 4240 LeAudioService leAudioService = mServiceFactory.getLeAudioService(); 4241 4242 if (leAudioService == null) { 4243 Log.e(TAG, "no LeAudioService"); 4244 return false; 4245 } 4246 4247 for (BluetoothDevice device : getConnectedDevices()) { 4248 if (!leAudioService.isPrimaryDevice(device)) { 4249 continue; 4250 } 4251 4252 Map<Integer, BluetoothLeBroadcastMetadata> entry = mBroadcastMetadataMap.get(device); 4253 4254 /* null means that this source was not added or modified by assistant */ 4255 if (entry == null) { 4256 continue; 4257 } 4258 4259 /* Assistant manages some external broadcast */ 4260 if (entry.values().stream().anyMatch(e -> !isLocalBroadcast(e))) { 4261 return true; 4262 } 4263 } 4264 4265 return false; 4266 } 4267 4268 /** Check if any sink receivers are receiving broadcast stream */ 4269 public boolean areReceiversReceivingOnlyExternalBroadcast(List<BluetoothDevice> devices) { 4270 boolean isReceivingExternalBroadcast = false; 4271 4272 for (BluetoothDevice device : devices) { 4273 for (BluetoothLeBroadcastReceiveState receiveState : getAllSources(device)) { 4274 for (int i = 0; i < receiveState.getNumSubgroups(); i++) { 4275 if (isSyncedToBroadcastStream(receiveState.getBisSyncState().get(i))) { 4276 if (isLocalBroadcast(receiveState)) { 4277 return false; 4278 } else { 4279 isReceivingExternalBroadcast = true; 4280 } 4281 } 4282 } 4283 } 4284 } 4285 4286 return isReceivingExternalBroadcast; 4287 } 4288 4289 private boolean isAnyReceiverSyncedToBroadcast(int broadcastId) { 4290 for (BluetoothDevice device : getConnectedDevices()) { 4291 if (getAllSources(device).stream() 4292 .anyMatch(receiveState -> (receiveState.getBroadcastId() == broadcastId))) { 4293 return true; 4294 } 4295 } 4296 return false; 4297 } 4298 4299 private static boolean isReceiverActive(BluetoothLeBroadcastReceiveState receiveState) { 4300 if (receiveState.getPaSyncState() 4301 == BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED) { 4302 return true; 4303 } else { 4304 for (int i = 0; i < receiveState.getNumSubgroups(); i++) { 4305 if (isSyncedToBroadcastStream(receiveState.getBisSyncState().get(i))) { 4306 return true; 4307 } 4308 } 4309 } 4310 return false; 4311 } 4312 4313 private Set<Integer> getExternalBroadcastsActiveOnSinks() { 4314 HashSet<Integer> syncedBroadcasts = new HashSet<>(); 4315 for (BluetoothDevice device : getConnectedDevices()) { 4316 for (BluetoothLeBroadcastReceiveState receiveState : getAllSources(device)) { 4317 if (isLocalBroadcast(receiveState)) { 4318 continue; 4319 } 4320 if (isReceiverActive(receiveState)) { 4321 syncedBroadcasts.add(receiveState.getBroadcastId()); 4322 log("getExternalBroadcastsActiveOnSinks: " + receiveState); 4323 } 4324 } 4325 } 4326 return syncedBroadcasts; 4327 } 4328 4329 private boolean isAllReceiversActive(int broadcastId) { 4330 for (BluetoothDevice device : getConnectedDevices()) { 4331 for (BluetoothLeBroadcastReceiveState receiveState : getAllSources(device)) { 4332 if (receiveState.getBroadcastId() == broadcastId 4333 && !isReceiverActive(receiveState)) { 4334 return false; 4335 } 4336 } 4337 } 4338 return true; 4339 } 4340 4341 /** Get sink devices synced to the broadcasts */ 4342 public List<BluetoothDevice> getSyncedBroadcastSinks() { 4343 List<BluetoothDevice> activeSinks = new ArrayList<>(); 4344 4345 for (BluetoothDevice device : getConnectedDevices()) { 4346 if (leaudioBigDependsOnAudioState()) { 4347 if (!getAllSources(device).isEmpty()) { 4348 activeSinks.add(device); 4349 } 4350 } else { 4351 if (getAllSources(device).stream() 4352 .anyMatch( 4353 receiveState -> 4354 (receiveState.getBisSyncState().stream() 4355 .anyMatch( 4356 BassClientService 4357 ::isSyncedToBroadcastStream)))) { 4358 activeSinks.add(device); 4359 } 4360 } 4361 } 4362 return activeSinks; 4363 } 4364 4365 /** Get sink devices synced to the broadcasts by broadcast id */ 4366 public List<BluetoothDevice> getSyncedBroadcastSinks(int broadcastId) { 4367 return getConnectedDevices().stream() 4368 .filter( 4369 device -> 4370 getAllSources(device).stream() 4371 .anyMatch(rs -> rs.getBroadcastId() == broadcastId)) 4372 .collect(Collectors.toUnmodifiableList()); 4373 } 4374 4375 private static boolean isSyncedToBroadcastStream(Long syncState) { 4376 return syncState != BassConstants.BCAST_RCVR_STATE_BIS_SYNC_NOT_SYNC_TO_BIS 4377 && syncState != BassConstants.BCAST_RCVR_STATE_BIS_SYNC_FAILED_SYNC_TO_BIG; 4378 } 4379 4380 private Set<Integer> getBroadcastIdsOfSyncedBroadcasters() { 4381 return getActiveSyncedSources().stream() 4382 .map(this::getBroadcastIdForSyncHandle) 4383 .collect(Collectors.toCollection(HashSet::new)); 4384 } 4385 4386 private Set<Integer> getBroadcastIdsWaitingForPAST() { 4387 synchronized (mSinksWaitingForPast) { 4388 return mSinksWaitingForPast.values().stream() 4389 .map(pair -> pair.first) 4390 .collect(Collectors.toCollection(HashSet::new)); 4391 } 4392 } 4393 4394 private Set<Integer> getBroadcastIdsWaitingForAddSource() { 4395 synchronized (mPendingSourcesToAdd) { 4396 return mPendingSourcesToAdd.stream() 4397 .map(pendingSource -> pendingSource.sourceMetadata.getBroadcastId()) 4398 .collect(Collectors.toCollection(HashSet::new)); 4399 } 4400 } 4401 4402 private Set<Integer> getPausedBroadcastIdsBasedOnSinks() { 4403 return mPausedBroadcastSinks.stream() 4404 .map(paused -> mBroadcastMetadataMap.getOrDefault(paused, Collections.emptyMap())) 4405 .flatMap(entry -> entry.keySet().stream()) 4406 .collect(Collectors.toCollection(HashSet::new)); 4407 } 4408 4409 private Set<Integer> getUnintentionallyPausedBroadcastIds() { 4410 return mPausedBroadcastIds.keySet().stream() 4411 .filter(this::isSinkUnintentionalPauseType) 4412 .collect(Collectors.toCollection(HashSet::new)); 4413 } 4414 4415 /** Handle broadcast state changed */ 4416 public void notifyBroadcastStateChanged(int state, int broadcastId) { 4417 switch (state) { 4418 case BROADCAST_STATE_STOPPED: 4419 if (mLocalBroadcastReceivers.remove(broadcastId) != null) { 4420 sEventLogger.logd(TAG, "Broadcast ID: " + broadcastId + ", stopped"); 4421 } 4422 break; 4423 case BROADCAST_STATE_CONFIGURING: 4424 case BROADCAST_STATE_PAUSED: 4425 case BROADCAST_STATE_ENABLING: 4426 case BROADCAST_STATE_DISABLING: 4427 case BROADCAST_STATE_STOPPING: 4428 case BROADCAST_STATE_STREAMING: 4429 default: 4430 break; 4431 } 4432 } 4433 4434 /** Callback handler */ 4435 static class Callbacks extends Handler { 4436 private static final int MSG_SEARCH_STARTED = 1; 4437 private static final int MSG_SEARCH_STARTED_FAILED = 2; 4438 private static final int MSG_SEARCH_STOPPED = 3; 4439 private static final int MSG_SEARCH_STOPPED_FAILED = 4; 4440 private static final int MSG_SOURCE_FOUND = 5; 4441 private static final int MSG_SOURCE_ADDED = 6; 4442 private static final int MSG_SOURCE_ADDED_FAILED = 7; 4443 private static final int MSG_SOURCE_MODIFIED = 8; 4444 private static final int MSG_SOURCE_MODIFIED_FAILED = 9; 4445 private static final int MSG_SOURCE_REMOVED = 10; 4446 private static final int MSG_SOURCE_REMOVED_FAILED = 11; 4447 private static final int MSG_RECEIVESTATE_CHANGED = 12; 4448 private static final int MSG_SOURCE_LOST = 13; 4449 private static final int MSG_BASS_STATE_READY = 14; 4450 private static final int MSG_BASS_STATE_SETUP_FAILED = 15; 4451 4452 @GuardedBy("mCallbacksList") 4453 private final RemoteCallbackList<IBluetoothLeBroadcastAssistantCallback> mCallbacksList = 4454 new RemoteCallbackList<>(); 4455 4456 Callbacks(Looper looper) { 4457 super(looper); 4458 } 4459 4460 public void register(IBluetoothLeBroadcastAssistantCallback callback) { 4461 synchronized (mCallbacksList) { 4462 mCallbacksList.register(callback); 4463 } 4464 } 4465 4466 public void unregister(IBluetoothLeBroadcastAssistantCallback callback) { 4467 synchronized (mCallbacksList) { 4468 mCallbacksList.unregister(callback); 4469 } 4470 } 4471 4472 private static void checkForPendingGroupOpRequest(Message msg) { 4473 if (sService == null) { 4474 Log.e(TAG, "Service is null"); 4475 return; 4476 } 4477 4478 final int reason = msg.arg1; 4479 BluetoothDevice sink; 4480 4481 switch (msg.what) { 4482 case MSG_SOURCE_ADDED: 4483 case MSG_SOURCE_ADDED_FAILED: 4484 ObjParams param = (ObjParams) msg.obj; 4485 sink = param.device; 4486 sService.checkForPendingGroupOpRequest( 4487 sink, reason, BassClientStateMachine.ADD_BCAST_SOURCE, param.obj2); 4488 break; 4489 case MSG_SOURCE_REMOVED: 4490 case MSG_SOURCE_REMOVED_FAILED: 4491 sink = (BluetoothDevice) msg.obj; 4492 sService.checkForPendingGroupOpRequest( 4493 sink, 4494 reason, 4495 BassClientStateMachine.REMOVE_BCAST_SOURCE, 4496 Integer.valueOf(msg.arg2)); 4497 break; 4498 default: 4499 break; 4500 } 4501 } 4502 4503 private static boolean handleServiceInternalMessage(Message msg) { 4504 boolean isMsgHandled = false; 4505 if (sService == null) { 4506 Log.e(TAG, "Service is null"); 4507 return isMsgHandled; 4508 } 4509 BluetoothDevice sink; 4510 4511 switch (msg.what) { 4512 case MSG_BASS_STATE_READY: 4513 sink = (BluetoothDevice) msg.obj; 4514 sService.handleBassStateReady(sink); 4515 isMsgHandled = true; 4516 break; 4517 case MSG_BASS_STATE_SETUP_FAILED: 4518 sink = (BluetoothDevice) msg.obj; 4519 sService.handleBassStateSetupFailed(sink); 4520 isMsgHandled = true; 4521 break; 4522 default: 4523 break; 4524 } 4525 return isMsgHandled; 4526 } 4527 4528 @Override 4529 public void handleMessage(Message msg) { 4530 if (handleServiceInternalMessage(msg)) { 4531 log("Handled internal message: " + msg.what); 4532 return; 4533 } 4534 4535 checkForPendingGroupOpRequest(msg); 4536 4537 synchronized (mCallbacksList) { 4538 final int n = mCallbacksList.beginBroadcast(); 4539 for (int i = 0; i < n; i++) { 4540 final IBluetoothLeBroadcastAssistantCallback callback = 4541 mCallbacksList.getBroadcastItem(i); 4542 try { 4543 invokeCallback(callback, msg); 4544 } catch (RemoteException e) { 4545 // Ignore exception 4546 } 4547 } 4548 mCallbacksList.finishBroadcast(); 4549 } 4550 } 4551 4552 private record ObjParams(BluetoothDevice device, Object obj2) {} 4553 4554 private static void invokeCallback( 4555 IBluetoothLeBroadcastAssistantCallback callback, Message msg) 4556 throws RemoteException { 4557 final int reason = msg.arg1; 4558 final int sourceId = msg.arg2; 4559 ObjParams param; 4560 BluetoothDevice sink; 4561 4562 switch (msg.what) { 4563 case MSG_SEARCH_STARTED: 4564 callback.onSearchStarted(reason); 4565 break; 4566 case MSG_SEARCH_STARTED_FAILED: 4567 callback.onSearchStartFailed(reason); 4568 break; 4569 case MSG_SEARCH_STOPPED: 4570 callback.onSearchStopped(reason); 4571 break; 4572 case MSG_SEARCH_STOPPED_FAILED: 4573 callback.onSearchStopFailed(reason); 4574 break; 4575 case MSG_SOURCE_FOUND: 4576 callback.onSourceFound((BluetoothLeBroadcastMetadata) msg.obj); 4577 break; 4578 case MSG_SOURCE_ADDED: 4579 param = (ObjParams) msg.obj; 4580 sink = param.device; 4581 callback.onSourceAdded(sink, sourceId, reason); 4582 break; 4583 case MSG_SOURCE_ADDED_FAILED: 4584 param = (ObjParams) msg.obj; 4585 sink = param.device; 4586 BluetoothLeBroadcastMetadata metadata = 4587 (BluetoothLeBroadcastMetadata) param.obj2; 4588 callback.onSourceAddFailed(sink, metadata, reason); 4589 break; 4590 case MSG_SOURCE_MODIFIED: 4591 callback.onSourceModified((BluetoothDevice) msg.obj, sourceId, reason); 4592 break; 4593 case MSG_SOURCE_MODIFIED_FAILED: 4594 callback.onSourceModifyFailed((BluetoothDevice) msg.obj, sourceId, reason); 4595 break; 4596 case MSG_SOURCE_REMOVED: 4597 sink = (BluetoothDevice) msg.obj; 4598 callback.onSourceRemoved(sink, sourceId, reason); 4599 break; 4600 case MSG_SOURCE_REMOVED_FAILED: 4601 sink = (BluetoothDevice) msg.obj; 4602 callback.onSourceRemoveFailed(sink, sourceId, reason); 4603 break; 4604 case MSG_RECEIVESTATE_CHANGED: 4605 param = (ObjParams) msg.obj; 4606 sink = param.device; 4607 BluetoothLeBroadcastReceiveState state = 4608 (BluetoothLeBroadcastReceiveState) param.obj2; 4609 callback.onReceiveStateChanged(sink, sourceId, state); 4610 break; 4611 case MSG_SOURCE_LOST: 4612 callback.onSourceLost(sourceId); 4613 break; 4614 default: 4615 Log.e(TAG, "Invalid msg: " + msg.what); 4616 break; 4617 } 4618 } 4619 4620 void notifySearchStarted(int reason) { 4621 sEventLogger.logd(TAG, "notifySearchStarted: reason: " + reason); 4622 obtainMessage(MSG_SEARCH_STARTED, reason, 0).sendToTarget(); 4623 } 4624 4625 void notifySearchStartFailed(int reason) { 4626 sEventLogger.loge(TAG, "notifySearchStartFailed: reason: " + reason); 4627 obtainMessage(MSG_SEARCH_STARTED_FAILED, reason, 0).sendToTarget(); 4628 } 4629 4630 void notifySearchStopped(int reason) { 4631 sEventLogger.logd(TAG, "notifySearchStopped: reason: " + reason); 4632 obtainMessage(MSG_SEARCH_STOPPED, reason, 0).sendToTarget(); 4633 } 4634 4635 void notifySearchStopFailed(int reason) { 4636 sEventLogger.loge(TAG, "notifySearchStopFailed: reason: " + reason); 4637 obtainMessage(MSG_SEARCH_STOPPED_FAILED, reason, 0).sendToTarget(); 4638 } 4639 4640 void notifySourceFound(BluetoothLeBroadcastMetadata source) { 4641 sEventLogger.logd( 4642 TAG, 4643 "invokeCallback: MSG_SOURCE_FOUND" 4644 + ", source: " 4645 + source.getSourceDevice() 4646 + ", broadcastId: " 4647 + source.getBroadcastId() 4648 + ", broadcastName: " 4649 + source.getBroadcastName() 4650 + ", isPublic: " 4651 + source.isPublicBroadcast() 4652 + ", isEncrypted: " 4653 + source.isEncrypted()); 4654 obtainMessage(MSG_SOURCE_FOUND, 0, 0, source).sendToTarget(); 4655 } 4656 4657 void notifySourceAdded( 4658 BluetoothDevice sink, BluetoothLeBroadcastReceiveState recvState, int reason) { 4659 sService.localNotifySourceAdded(sink, recvState); 4660 4661 sEventLogger.logd( 4662 TAG, 4663 "notifySourceAdded: " 4664 + "sink: " 4665 + sink 4666 + ", sourceId: " 4667 + recvState.getSourceId() 4668 + ", reason: " 4669 + reason); 4670 4671 ObjParams param = new ObjParams(sink, recvState); 4672 obtainMessage(MSG_SOURCE_ADDED, reason, recvState.getSourceId(), param).sendToTarget(); 4673 } 4674 4675 void notifySourceAddFailed( 4676 BluetoothDevice sink, BluetoothLeBroadcastMetadata source, int reason) { 4677 sService.checkAndResetGroupAllowedContextMask(); 4678 sService.localNotifySourceAddFailed(sink, source); 4679 4680 sEventLogger.loge( 4681 TAG, 4682 "notifySourceAddFailed: sink: " 4683 + sink 4684 + ", source: " 4685 + source 4686 + ", reason: " 4687 + reason); 4688 ObjParams param = new ObjParams(sink, source); 4689 obtainMessage(MSG_SOURCE_ADDED_FAILED, reason, 0, param).sendToTarget(); 4690 } 4691 4692 void notifySourceModified(BluetoothDevice sink, int sourceId, int reason) { 4693 sEventLogger.logd( 4694 TAG, 4695 "notifySourceModified: " 4696 + "sink: " 4697 + sink 4698 + ", sourceId: " 4699 + sourceId 4700 + ", reason: " 4701 + reason); 4702 obtainMessage(MSG_SOURCE_MODIFIED, reason, sourceId, sink).sendToTarget(); 4703 } 4704 4705 void notifySourceModifyFailed(BluetoothDevice sink, int sourceId, int reason) { 4706 sEventLogger.loge( 4707 TAG, 4708 "notifySourceModifyFailed: sink: " 4709 + sink 4710 + ", sourceId: " 4711 + sourceId 4712 + ", reason: " 4713 + reason); 4714 obtainMessage(MSG_SOURCE_MODIFIED_FAILED, reason, sourceId, sink).sendToTarget(); 4715 } 4716 4717 void notifySourceRemoved(BluetoothDevice sink, int sourceId, int reason) { 4718 sEventLogger.logd( 4719 TAG, 4720 "notifySourceRemoved: " 4721 + "sink: " 4722 + sink 4723 + ", sourceId: " 4724 + sourceId 4725 + ", reason: " 4726 + reason); 4727 obtainMessage(MSG_SOURCE_REMOVED, reason, sourceId, sink).sendToTarget(); 4728 } 4729 4730 void notifySourceRemoveFailed(BluetoothDevice sink, int sourceId, int reason) { 4731 sEventLogger.loge( 4732 TAG, 4733 "notifySourceRemoveFailed: " 4734 + "sink: " 4735 + sink 4736 + ", sourceId: " 4737 + sourceId 4738 + ", reason: " 4739 + reason); 4740 obtainMessage(MSG_SOURCE_REMOVED_FAILED, reason, sourceId, sink).sendToTarget(); 4741 } 4742 4743 void notifyReceiveStateChanged( 4744 BluetoothDevice sink, int sourceId, BluetoothLeBroadcastReceiveState state) { 4745 ObjParams param = new ObjParams(sink, state); 4746 4747 sService.localNotifyReceiveStateChanged(sink, state); 4748 4749 StringBuilder subgroupState = new StringBuilder(" / SUB GROUPS: "); 4750 for (int i = 0; i < state.getNumSubgroups(); i++) { 4751 subgroupState 4752 .append("IDX: ") 4753 .append(i) 4754 .append(", SYNC: ") 4755 .append(state.getBisSyncState().get(i)); 4756 } 4757 4758 sEventLogger.logd( 4759 TAG, 4760 "notifyReceiveStateChanged: " 4761 + "sink: " 4762 + sink 4763 + ", state: SRC ID: " 4764 + state.getSourceId() 4765 + " / ADDR TYPE: " 4766 + state.getSourceAddressType() 4767 + " / SRC DEV: " 4768 + state.getSourceDevice() 4769 + " / ADV SID: " 4770 + state.getSourceAdvertisingSid() 4771 + " / BID: " 4772 + state.getBroadcastId() 4773 + " / PA STATE: " 4774 + state.getPaSyncState() 4775 + " / BENC STATE: " 4776 + state.getBigEncryptionState() 4777 + " / BAD CODE: " 4778 + Arrays.toString(state.getBadCode()) 4779 + subgroupState.toString()); 4780 obtainMessage(MSG_RECEIVESTATE_CHANGED, 0, sourceId, param).sendToTarget(); 4781 } 4782 4783 void notifySourceLost(int broadcastId) { 4784 sEventLogger.logd(TAG, "notifySourceLost: broadcastId: " + broadcastId); 4785 obtainMessage(MSG_SOURCE_LOST, 0, broadcastId).sendToTarget(); 4786 } 4787 4788 void notifyBassStateReady(BluetoothDevice sink) { 4789 sEventLogger.logd(TAG, "notifyBassStateReady: sink: " + sink); 4790 obtainMessage(MSG_BASS_STATE_READY, sink).sendToTarget(); 4791 } 4792 4793 void notifyBassStateSetupFailed(BluetoothDevice sink) { 4794 sEventLogger.logd(TAG, "notifyBassStateSetupFailed: sink: " + sink); 4795 obtainMessage(MSG_BASS_STATE_SETUP_FAILED, sink).sendToTarget(); 4796 } 4797 } 4798 4799 @Override 4800 public void dump(StringBuilder sb) { 4801 super.dump(sb); 4802 4803 sb.append("Broadcast Assistant Service instance:\n"); 4804 4805 /* Dump first connected state machines */ 4806 for (Map.Entry<BluetoothDevice, BassClientStateMachine> entry : mStateMachines.entrySet()) { 4807 BassClientStateMachine sm = entry.getValue(); 4808 if (sm.getConnectionState() == STATE_CONNECTED) { 4809 sm.dump(sb); 4810 sb.append("\n\n"); 4811 } 4812 } 4813 4814 /* Dump at least all other than connected state machines */ 4815 for (Map.Entry<BluetoothDevice, BassClientStateMachine> entry : mStateMachines.entrySet()) { 4816 BassClientStateMachine sm = entry.getValue(); 4817 if (sm.getConnectionState() != STATE_CONNECTED) { 4818 sm.dump(sb); 4819 } 4820 } 4821 4822 sb.append("\n\n"); 4823 sEventLogger.dump(sb); 4824 sb.append("\n"); 4825 } 4826 } 4827