• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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