1 /*
2  * Copyright 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.Manifest.permission.BLUETOOTH_CONNECT;
20 import static android.bluetooth.IBluetoothLeAudio.LE_AUDIO_GROUP_ID_INVALID;
21 
22 import static com.android.bluetooth.Utils.enforceBluetoothPrivilegedPermission;
23 import static com.android.bluetooth.flags.Flags.leaudioAllowedContextMask;
24 import static com.android.bluetooth.flags.Flags.leaudioBroadcastAssistantPeripheralEntrustment;
25 import static com.android.bluetooth.flags.Flags.leaudioBroadcastAudioHandoverPolicies;
26 import static com.android.bluetooth.flags.Flags.leaudioBroadcastExtractPeriodicScannerFromStateMachine;
27 import static com.android.bluetooth.flags.Flags.leaudioBroadcastFeatureSupport;
28 import static com.android.bluetooth.flags.Flags.leaudioBroadcastMonitorSourceSyncStatus;
29 
30 import android.bluetooth.BluetoothAdapter;
31 import android.bluetooth.BluetoothDevice;
32 import android.bluetooth.BluetoothGatt;
33 import android.bluetooth.BluetoothLeAudio;
34 import android.bluetooth.BluetoothLeAudioCodecConfigMetadata;
35 import android.bluetooth.BluetoothLeAudioContentMetadata;
36 import android.bluetooth.BluetoothLeBroadcastChannel;
37 import android.bluetooth.BluetoothLeBroadcastMetadata;
38 import android.bluetooth.BluetoothLeBroadcastReceiveState;
39 import android.bluetooth.BluetoothLeBroadcastSubgroup;
40 import android.bluetooth.BluetoothProfile;
41 import android.bluetooth.BluetoothStatusCodes;
42 import android.bluetooth.BluetoothUtils;
43 import android.bluetooth.BluetoothUtils.TypeValueEntry;
44 import android.bluetooth.BluetoothUuid;
45 import android.bluetooth.IBluetoothLeBroadcastAssistant;
46 import android.bluetooth.IBluetoothLeBroadcastAssistantCallback;
47 import android.bluetooth.le.PeriodicAdvertisingCallback;
48 import android.bluetooth.le.PeriodicAdvertisingReport;
49 import android.bluetooth.le.ScanCallback;
50 import android.bluetooth.le.ScanFilter;
51 import android.bluetooth.le.ScanRecord;
52 import android.bluetooth.le.ScanResult;
53 import android.bluetooth.le.ScanSettings;
54 import android.content.Context;
55 import android.os.Handler;
56 import android.os.HandlerThread;
57 import android.os.Looper;
58 import android.os.Message;
59 import android.os.ParcelUuid;
60 import android.os.RemoteCallbackList;
61 import android.os.RemoteException;
62 import android.provider.DeviceConfig;
63 import android.sysprop.BluetoothProperties;
64 import android.util.Log;
65 import android.util.Pair;
66 
67 import com.android.bluetooth.BluetoothEventLogger;
68 import com.android.bluetooth.BluetoothMethodProxy;
69 import com.android.bluetooth.Utils;
70 import com.android.bluetooth.btservice.AdapterService;
71 import com.android.bluetooth.btservice.ProfileService;
72 import com.android.bluetooth.btservice.ServiceFactory;
73 import com.android.bluetooth.btservice.storage.DatabaseManager;
74 import com.android.bluetooth.csip.CsipSetCoordinatorService;
75 import com.android.bluetooth.le_audio.LeAudioService;
76 import com.android.internal.annotations.VisibleForTesting;
77 
78 import java.nio.charset.StandardCharsets;
79 import java.time.Duration;
80 import java.util.ArrayDeque;
81 import java.util.ArrayList;
82 import java.util.Arrays;
83 import java.util.Collections;
84 import java.util.Comparator;
85 import java.util.Deque;
86 import java.util.HashMap;
87 import java.util.HashSet;
88 import java.util.Iterator;
89 import java.util.LinkedList;
90 import java.util.List;
91 import java.util.Map;
92 import java.util.Objects;
93 import java.util.Optional;
94 import java.util.PriorityQueue;
95 import java.util.concurrent.ConcurrentHashMap;
96 
97 /** Broacast Assistant Scan Service */
98 public class BassClientService extends ProfileService {
99     private static final String TAG = BassClientService.class.getSimpleName();
100     private static final int MAX_BASS_CLIENT_STATE_MACHINES = 10;
101     private static final int MAX_ACTIVE_SYNCED_SOURCES_NUM = 4;
102     private static final int MAX_BIS_DISCOVERY_TRIES_NUM = 5;
103 
104     private static final int STATUS_LOCAL_STREAM_REQUESTED = 0;
105     private static final int STATUS_LOCAL_STREAM_STREAMING = 1;
106     private static final int STATUS_LOCAL_STREAM_SUSPENDED = 2;
107     private static final int STATUS_LOCAL_STREAM_REQUESTED_NO_CONTEXT_VALIDATE = 3;
108 
109     // Do not modify without updating the HAL bt_le_audio.h files.
110     // Match up with BroadcastState enum of bt_le_audio.h
111     private static final int BROADCAST_STATE_STOPPED = 0;
112     private static final int BROADCAST_STATE_CONFIGURING = 1;
113     private static final int BROADCAST_STATE_PAUSED = 2;
114     private static final int BROADCAST_STATE_STOPPING = 3;
115     private static final int BROADCAST_STATE_STREAMING = 4;
116 
117     private static final int MESSAGE_SYNC_TIMEOUT = 1;
118 
119     /* 1 minute timeout for primary device reconnection in Private Broadcast case */
120     private static final int DIALING_OUT_TIMEOUT_MS = 60000;
121 
122     // 30 secs timeout for keeping PSYNC active when searching is stopped
123     @VisibleForTesting static Duration sSyncActiveTimeout = Duration.ofSeconds(30);
124 
125     private static BassClientService sService;
126 
127     private final Map<BluetoothDevice, BassClientStateMachine> mStateMachines = new HashMap<>();
128     private final Object mSearchScanCallbackLock = new Object();
129     private final Map<Integer, ScanResult> mCachedBroadcasts = new HashMap<>();
130 
131     private final List<Integer> mActiveSyncedSources = new ArrayList<>();
132     private final Map<Integer, PeriodicAdvertisingCallback> mPeriodicAdvCallbacksMap =
133             new HashMap<>();
134     private final PriorityQueue<SourceSyncRequest> mSourceSyncRequestsQueue =
135             new PriorityQueue<>(sSourceSyncRequestComparator);
136     private final Map<Integer, Integer> mBisDiscoveryCounterMap = new HashMap<Integer, Integer>();
137     private final List<AddSourceData> mPendingSourcesToAdd = new ArrayList<AddSourceData>();
138 
139     private final Map<BluetoothDevice, List<Pair<Integer, Object>>> mPendingGroupOp =
140             new ConcurrentHashMap<>();
141     private final Map<BluetoothDevice, List<Integer>> mGroupManagedSources =
142             new ConcurrentHashMap<>();
143     private final Map<BluetoothDevice, List<Integer>> mActiveSourceMap = new ConcurrentHashMap<>();
144     private final Map<BluetoothDevice, BluetoothLeBroadcastMetadata> mBroadcastMetadataMap =
145             new ConcurrentHashMap<>();
146     private final LinkedList<BluetoothDevice> mPausedBroadcastSinks = new LinkedList<>();
147     private final Deque<AddSourceData> mPendingAddSources = new ArrayDeque<>();
148     private final Map<Integer, HashSet<BluetoothDevice>> mLocalBroadcastReceivers =
149             new ConcurrentHashMap<>();
150 
151     private HandlerThread mStateMachinesThread;
152     private HandlerThread mCallbackHandlerThread;
153     private AdapterService mAdapterService;
154     private DatabaseManager mDatabaseManager;
155     private BluetoothAdapter mBluetoothAdapter = null;
156     private BluetoothLeScannerWrapper mBluetoothLeScannerWrapper = null;
157     private DialingOutTimeoutEvent mDialingOutTimeoutEvent = null;
158 
159     /* Caching the PeriodicAdvertisementResult from Broadcast source */
160     /* This is stored at service so that each device state machine can access
161     and use it as needed. Once the periodic sync in cancelled, this data will bre
162     removed to ensure stable data won't used */
163     /* syncHandle, broadcastSrcDevice */
164     private Map<Integer, BluetoothDevice> mSyncHandleToDeviceMap =
165             new HashMap<Integer, BluetoothDevice>();
166     /*syncHandle, parsed BaseData data*/
167     private Map<Integer, BaseData> mSyncHandleToBaseDataMap = new HashMap<Integer, BaseData>();
168     /*syncHandle, broadcast id */
169     private Map<Integer, Integer> mSyncHandleToBroadcastIdMap = new HashMap<Integer, Integer>();
170     /*bcastSrcDevice, corresponding broadcast id and PeriodicAdvertisementResult*/
171     private Map<BluetoothDevice, HashMap<Integer, PeriodicAdvertisementResult>>
172             mPeriodicAdvertisementResultMap =
173                     new HashMap<BluetoothDevice, HashMap<Integer, PeriodicAdvertisementResult>>();
174     private ScanCallback mSearchScanCallback = null;
175     private Callbacks mCallbacks;
176     private boolean mIsAssistantActive = false;
177     private boolean mIsAllowedContextOfActiveGroupModified = false;
178     Optional<Integer> mUnicastSourceStreamStatus = Optional.empty();
179 
180     private static final int LOG_NB_EVENTS = 100;
181     private static final BluetoothEventLogger sEventLogger =
182             new BluetoothEventLogger(LOG_NB_EVENTS, TAG + " event log");
183     ;
184 
185     @VisibleForTesting ServiceFactory mServiceFactory = new ServiceFactory();
186 
187     private final Handler mHandler =
188             new Handler(Looper.getMainLooper()) {
189                 @Override
190                 public void handleMessage(Message msg) {
191                     switch (msg.what) {
192                         case MESSAGE_SYNC_TIMEOUT:
193                             log("MESSAGE_SYNC_TIMEOUT: clear all sync data");
194                             clearAllSyncData();
195                             break;
196                     }
197                 }
198             };
199 
BassClientService(Context ctx)200     public BassClientService(Context ctx) {
201         super(ctx);
202     }
203 
isEnabled()204     public static boolean isEnabled() {
205         return leaudioBroadcastFeatureSupport()
206                 && BluetoothProperties.isProfileBapBroadcastAssistEnabled().orElse(false);
207     }
208 
209     private static class SourceSyncRequest {
210         private ScanResult mScanResult;
211         private boolean mHasPriority;
212 
SourceSyncRequest(ScanResult scanResult, boolean hasPriority)213         SourceSyncRequest(ScanResult scanResult, boolean hasPriority) {
214             this.mScanResult = scanResult;
215             this.mHasPriority = hasPriority;
216         }
217 
getScanResult()218         public ScanResult getScanResult() {
219             return mScanResult;
220         }
221 
getRssi()222         public int getRssi() {
223             return mScanResult.getRssi();
224         }
225 
hasPriority()226         public boolean hasPriority() {
227             return mHasPriority;
228         }
229 
230         @Override
toString()231         public String toString() {
232             return "SourceSyncRequest{"
233                     + "mScanResult="
234                     + mScanResult
235                     + ", mHasPriority="
236                     + mHasPriority
237                     + '}';
238         }
239     }
240 
241     private static final Comparator<SourceSyncRequest> sSourceSyncRequestComparator =
242             new Comparator<SourceSyncRequest>() {
243                 @Override
244                 public int compare(SourceSyncRequest ssr1, SourceSyncRequest ssr2) {
245                     if (ssr1.hasPriority() && !ssr2.hasPriority()) {
246                         return -1;
247                     } else if (!ssr1.hasPriority() && ssr2.hasPriority()) {
248                         return 1;
249                     } else {
250                         return Integer.compare(ssr2.getRssi(), ssr1.getRssi());
251                     }
252                 }
253             };
254 
255     private static class AddSourceData {
256         BluetoothDevice mSink;
257         BluetoothLeBroadcastMetadata mSourceMetadata;
258         boolean mIsGroupOp;
259 
AddSourceData( BluetoothDevice sink, BluetoothLeBroadcastMetadata sourceMetadata, boolean isGroupOp)260         AddSourceData(
261                 BluetoothDevice sink,
262                 BluetoothLeBroadcastMetadata sourceMetadata,
263                 boolean isGroupOp) {
264             mSink = sink;
265             mSourceMetadata = sourceMetadata;
266             mIsGroupOp = isGroupOp;
267         }
268     }
269 
updatePeriodicAdvertisementResultMap( BluetoothDevice device, int addressType, int syncHandle, int advSid, int advInterval, int bId, PublicBroadcastData pbData, String broadcastName)270     void updatePeriodicAdvertisementResultMap(
271             BluetoothDevice device,
272             int addressType,
273             int syncHandle,
274             int advSid,
275             int advInterval,
276             int bId,
277             PublicBroadcastData pbData,
278             String broadcastName) {
279         log("updatePeriodicAdvertisementResultMap: device: " + device);
280         log("updatePeriodicAdvertisementResultMap: syncHandle: " + syncHandle);
281         log("updatePeriodicAdvertisementResultMap: advSid: " + advSid);
282         log("updatePeriodicAdvertisementResultMap: addressType: " + addressType);
283         log("updatePeriodicAdvertisementResultMap: advInterval: " + advInterval);
284         log("updatePeriodicAdvertisementResultMap: broadcastId: " + bId);
285         log("updatePeriodicAdvertisementResultMap: broadcastName: " + broadcastName);
286         log("mSyncHandleToDeviceMap" + mSyncHandleToDeviceMap);
287         log("mPeriodicAdvertisementResultMap" + mPeriodicAdvertisementResultMap);
288         if (mPeriodicAdvertisementResultMap != null) {
289             HashMap<Integer, PeriodicAdvertisementResult> paResMap =
290                     mPeriodicAdvertisementResultMap.get(device);
291             if (paResMap == null
292                     || (bId != BassConstants.INVALID_BROADCAST_ID && !paResMap.containsKey(bId))) {
293                 log("PAResmap: add >>>");
294                 PeriodicAdvertisementResult paRes =
295                         new PeriodicAdvertisementResult(
296                                 device,
297                                 addressType,
298                                 syncHandle,
299                                 advSid,
300                                 advInterval,
301                                 bId,
302                                 pbData,
303                                 broadcastName);
304                 if (paRes != null) {
305                     paRes.print();
306                     mPeriodicAdvertisementResultMap.putIfAbsent(device, new HashMap<>());
307                     mPeriodicAdvertisementResultMap.get(device).put(bId, paRes);
308                 }
309             } else {
310                 log("PAResmap: update >>>");
311                 if (bId == BassConstants.INVALID_BROADCAST_ID) {
312                     // Update when onSyncEstablished, try to retrieve valid broadcast id
313                     if (leaudioBroadcastExtractPeriodicScannerFromStateMachine()) {
314                         bId = getBroadcastIdForSyncHandle(BassConstants.INVALID_SYNC_HANDLE);
315 
316                         if (bId == BassConstants.INVALID_BROADCAST_ID
317                                 || !paResMap.containsKey(bId)) {
318                             Log.e(TAG, "PAResmap: error! no valid broadcast id found>>>");
319                             return;
320                         }
321 
322                         int oldBroadcastId = getBroadcastIdForSyncHandle(syncHandle);
323                         if (oldBroadcastId != BassConstants.INVALID_BROADCAST_ID
324                                 && oldBroadcastId != bId) {
325                             log(
326                                     "updatePeriodicAdvertisementResultMap: SyncEstablished on the"
327                                             + " same syncHandle="
328                                             + syncHandle
329                                             + ", before syncLost");
330                             if (leaudioBroadcastMonitorSourceSyncStatus()) {
331                                 log(
332                                         "Notify broadcast source lost, broadcast id: "
333                                                 + oldBroadcastId);
334                                 mCallbacks.notifySourceLost(oldBroadcastId);
335                             }
336                             clearAllDataForSyncHandle(syncHandle);
337                             mCachedBroadcasts.remove(oldBroadcastId);
338                         }
339                     } else {
340                         for (Map.Entry<Integer, PeriodicAdvertisementResult> entry :
341                                 paResMap.entrySet()) {
342                             PeriodicAdvertisementResult value = entry.getValue();
343                             if (value.getBroadcastId() != BassConstants.INVALID_BROADCAST_ID) {
344                                 bId = value.getBroadcastId();
345                                 break;
346                             }
347                         }
348                         if (bId == BassConstants.INVALID_BROADCAST_ID) {
349                             log("PAResmap: error! no valid broadcast id found>>>");
350                             return;
351                         }
352                     }
353                 }
354                 PeriodicAdvertisementResult paRes = paResMap.get(bId);
355                 if (advSid != BassConstants.INVALID_ADV_SID) {
356                     paRes.updateAdvSid(advSid);
357                 }
358                 if (syncHandle != BassConstants.INVALID_SYNC_HANDLE) {
359                     if (mSyncHandleToDeviceMap != null) {
360                         mSyncHandleToDeviceMap.put(syncHandle, device);
361                     }
362                     paRes.updateSyncHandle(syncHandle);
363                     if (paRes.getBroadcastId() != BassConstants.INVALID_BROADCAST_ID) {
364                         // broadcast successfully synced
365                         // update the sync handle for the broadcast source
366                         updateSyncHandleForBroadcastId(syncHandle, paRes.getBroadcastId());
367                     }
368                 }
369                 if (addressType != BassConstants.INVALID_ADV_ADDRESS_TYPE) {
370                     paRes.updateAddressType(addressType);
371                 }
372                 if (advInterval != BassConstants.INVALID_ADV_INTERVAL) {
373                     paRes.updateAdvInterval(advInterval);
374                 }
375                 if (bId != BassConstants.INVALID_BROADCAST_ID) {
376                     paRes.updateBroadcastId(bId);
377                 }
378                 if (pbData != null) {
379                     paRes.updatePublicBroadcastData(pbData);
380                 }
381                 if (broadcastName != null) {
382                     paRes.updateBroadcastName(broadcastName);
383                 }
384                 paRes.print();
385                 paResMap.replace(bId, paRes);
386             }
387         }
388         log(">>mPeriodicAdvertisementResultMap" + mPeriodicAdvertisementResultMap);
389     }
390 
getPeriodicAdvertisementResult( BluetoothDevice device, int broadcastId)391     PeriodicAdvertisementResult getPeriodicAdvertisementResult(
392             BluetoothDevice device, int broadcastId) {
393         if (mPeriodicAdvertisementResultMap == null) {
394             Log.e(TAG, "getPeriodicAdvertisementResult: mPeriodicAdvertisementResultMap is null");
395             return null;
396         }
397 
398         if (broadcastId == BassConstants.INVALID_BROADCAST_ID) {
399             Log.e(TAG, "getPeriodicAdvertisementResult: invalid broadcast id");
400             return null;
401         }
402 
403         if (mPeriodicAdvertisementResultMap.containsKey(device)) {
404             return mPeriodicAdvertisementResultMap.get(device).get(broadcastId);
405         }
406         return null;
407     }
408 
clearNotifiedFlags()409     void clearNotifiedFlags() {
410         log("clearNotifiedFlags");
411         for (Map.Entry<BluetoothDevice, HashMap<Integer, PeriodicAdvertisementResult>> entry :
412                 mPeriodicAdvertisementResultMap.entrySet()) {
413             HashMap<Integer, PeriodicAdvertisementResult> value = entry.getValue();
414             for (PeriodicAdvertisementResult result : value.values()) {
415                 result.setNotified(false);
416                 result.print();
417             }
418         }
419     }
420 
updateBase(int syncHandlemap, BaseData base)421     void updateBase(int syncHandlemap, BaseData base) {
422         if (mSyncHandleToBaseDataMap == null) {
423             Log.e(TAG, "updateBase: mSyncHandleToBaseDataMap is null");
424             return;
425         }
426         log("updateBase : mSyncHandleToBaseDataMap>>");
427         mSyncHandleToBaseDataMap.put(syncHandlemap, base);
428     }
429 
getBase(int syncHandlemap)430     BaseData getBase(int syncHandlemap) {
431         if (mSyncHandleToBaseDataMap == null) {
432             Log.e(TAG, "getBase: mSyncHandleToBaseDataMap is null");
433             return null;
434         }
435         BaseData base = mSyncHandleToBaseDataMap.get(syncHandlemap);
436         log("getBase returns " + base);
437         return base;
438     }
439 
removeActiveSyncedSource(BluetoothDevice scanDelegator, Integer syncHandle)440     void removeActiveSyncedSource(BluetoothDevice scanDelegator, Integer syncHandle) {
441         if (leaudioBroadcastExtractPeriodicScannerFromStateMachine()) {
442             throw new RuntimeException(
443                     "Should never be executed with"
444                             + " leaudioBroadcastExtractPeriodicScannerFromStateMachine flag");
445         }
446         if (mActiveSourceMap == null) {
447             Log.e(TAG, "removeActiveSyncedSource: mActiveSourceMap is null");
448             return;
449         }
450 
451         log(
452                 "removeActiveSyncedSource, scanDelegator: "
453                         + scanDelegator
454                         + ", syncHandle: "
455                         + syncHandle);
456         if (syncHandle == null) {
457             // remove all sources for this scanDelegator
458             mActiveSourceMap.remove(scanDelegator);
459         } else {
460             List<Integer> sources = mActiveSourceMap.get(scanDelegator);
461             if (sources != null) {
462                 sources.removeIf(e -> e.equals(syncHandle));
463                 if (sources.isEmpty()) {
464                     mActiveSourceMap.remove(scanDelegator);
465                 }
466             }
467         }
468         sEventLogger.logd(
469                 TAG,
470                 "Broadcast Source Unsynced: scanDelegator= "
471                         + scanDelegator
472                         + ", syncHandle= "
473                         + syncHandle);
474     }
475 
addActiveSyncedSource(BluetoothDevice scanDelegator, Integer syncHandle)476     void addActiveSyncedSource(BluetoothDevice scanDelegator, Integer syncHandle) {
477         if (leaudioBroadcastExtractPeriodicScannerFromStateMachine()) {
478             throw new RuntimeException(
479                     "Should never be executed with"
480                             + " leaudioBroadcastExtractPeriodicScannerFromStateMachine flag");
481         }
482         if (mActiveSourceMap == null) {
483             Log.e(TAG, "addActiveSyncedSource: mActiveSourceMap is null");
484             return;
485         }
486 
487         log(
488                 "addActiveSyncedSource, scanDelegator: "
489                         + scanDelegator
490                         + ", syncHandle: "
491                         + syncHandle);
492         if (syncHandle != BassConstants.INVALID_SYNC_HANDLE) {
493             mActiveSourceMap.putIfAbsent(scanDelegator, new ArrayList<>());
494             if (!mActiveSourceMap.get(scanDelegator).contains(syncHandle)) {
495                 mActiveSourceMap.get(scanDelegator).add(syncHandle);
496             }
497         }
498         sEventLogger.logd(
499                 TAG,
500                 "Broadcast Source Synced: scanDelegator= "
501                         + scanDelegator
502                         + ", syncHandle= "
503                         + syncHandle);
504     }
505 
getActiveSyncedSources(BluetoothDevice scanDelegator)506     List<Integer> getActiveSyncedSources(BluetoothDevice scanDelegator) {
507         if (leaudioBroadcastExtractPeriodicScannerFromStateMachine()) {
508             throw new RuntimeException(
509                     "Should never be executed with"
510                             + " leaudioBroadcastExtractPeriodicScannerFromStateMachine flag");
511         }
512         if (mActiveSourceMap == null) {
513             Log.e(TAG, "getActiveSyncedSources: mActiveSourceMap is null");
514             return null;
515         }
516 
517         List<Integer> currentSources = mActiveSourceMap.get(scanDelegator);
518         if (currentSources != null) {
519             log(
520                     "getActiveSyncedSources: scanDelegator: "
521                             + scanDelegator
522                             + ", sources num: "
523                             + currentSources.size());
524         } else {
525             log(
526                     "getActiveSyncedSources: scanDelegator: "
527                             + scanDelegator
528                             + ", currentSources is null");
529         }
530         return currentSources;
531     }
532 
removeActiveSyncedSource(Integer syncHandle)533     void removeActiveSyncedSource(Integer syncHandle) {
534         log("removeActiveSyncedSource, syncHandle: " + syncHandle);
535         if (syncHandle == null) {
536             // remove all sources
537             mActiveSyncedSources.clear();
538         } else {
539             mActiveSyncedSources.removeIf(e -> e.equals(syncHandle));
540         }
541         sEventLogger.logd(TAG, "Broadcast Source Unsynced: syncHandle= " + syncHandle);
542     }
543 
addActiveSyncedSource(Integer syncHandle)544     void addActiveSyncedSource(Integer syncHandle) {
545         log("addActiveSyncedSource, syncHandle: " + syncHandle);
546         if (syncHandle != BassConstants.INVALID_SYNC_HANDLE) {
547             if (!mActiveSyncedSources.contains(syncHandle)) {
548                 mActiveSyncedSources.add(syncHandle);
549             }
550         }
551         sEventLogger.logd(TAG, "Broadcast Source Synced: syncHandle= " + syncHandle);
552     }
553 
getActiveSyncedSources()554     List<Integer> getActiveSyncedSources() {
555         log("getActiveSyncedSources: sources num: " + mActiveSyncedSources.size());
556         return mActiveSyncedSources;
557     }
558 
getCachedBroadcast(int broadcastId)559     ScanResult getCachedBroadcast(int broadcastId) {
560         return mCachedBroadcasts.get(broadcastId);
561     }
562 
getCallbacks()563     public Callbacks getCallbacks() {
564         return mCallbacks;
565     }
566 
567     @Override
initBinder()568     protected IProfileServiceBinder initBinder() {
569         return new BluetoothLeBroadcastAssistantBinder(this);
570     }
571 
572     @Override
start()573     public void start() {
574         Log.d(TAG, "start()");
575         if (sService != null) {
576             throw new IllegalStateException("start() called twice");
577         }
578         mAdapterService =
579                 Objects.requireNonNull(
580                         AdapterService.getAdapterService(),
581                         "AdapterService cannot be null when BassClientService starts");
582         mDatabaseManager =
583                 Objects.requireNonNull(
584                         mAdapterService.getDatabase(),
585                         "DatabaseManager cannot be null when BassClientService starts");
586         mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
587 
588         mStateMachines.clear();
589         mStateMachinesThread = new HandlerThread("BassClientService.StateMachines");
590         mStateMachinesThread.start();
591         mCallbackHandlerThread = new HandlerThread(TAG);
592         mCallbackHandlerThread.start();
593         mCallbacks = new Callbacks(mCallbackHandlerThread.getLooper());
594 
595         setBassClientService(this);
596         // While removing leaudioBroadcastExtractPeriodicScannerFromStateMachine remove all checks
597         // against null for: mSyncHandleToDeviceMap, mPeriodicAdvertisementResultMap,
598         // mSyncHandleToBaseDataMap, mSyncHandleToBroadcastIdMap as they never be null
599         if (!leaudioBroadcastExtractPeriodicScannerFromStateMachine()) {
600             // Saving PSync stuff for future addition
601             mSyncHandleToDeviceMap = new HashMap<Integer, BluetoothDevice>();
602             mPeriodicAdvertisementResultMap =
603                     new HashMap<BluetoothDevice, HashMap<Integer, PeriodicAdvertisementResult>>();
604             mSyncHandleToBaseDataMap = new HashMap<Integer, BaseData>();
605             mSyncHandleToBroadcastIdMap = new HashMap<Integer, Integer>();
606             mSearchScanCallback = null;
607         }
608     }
609 
610     @Override
stop()611     public void stop() {
612         Log.d(TAG, "stop()");
613 
614         mUnicastSourceStreamStatus = Optional.empty();
615 
616         if (mDialingOutTimeoutEvent != null) {
617             mHandler.removeCallbacks(mDialingOutTimeoutEvent);
618             mDialingOutTimeoutEvent = null;
619         }
620 
621         if (mIsAssistantActive) {
622             LeAudioService leAudioService = mServiceFactory.getLeAudioService();
623             if (leAudioService != null) {
624                 leAudioService.activeBroadcastAssistantNotification(false);
625             }
626             mIsAssistantActive = false;
627         }
628 
629         if (mIsAllowedContextOfActiveGroupModified) {
630             LeAudioService leAudioService = mServiceFactory.getLeAudioService();
631             if (leAudioService != null) {
632                 leAudioService.setActiveGroupAllowedContextMask(
633                         BluetoothLeAudio.CONTEXTS_ALL, BluetoothLeAudio.CONTEXTS_ALL);
634             }
635             mIsAllowedContextOfActiveGroupModified = false;
636         }
637 
638         synchronized (mStateMachines) {
639             for (BassClientStateMachine sm : mStateMachines.values()) {
640                 BassObjectsFactory.getInstance().destroyStateMachine(sm);
641             }
642             mStateMachines.clear();
643         }
644         if (mCallbackHandlerThread != null) {
645             mCallbackHandlerThread.quitSafely();
646             mCallbackHandlerThread = null;
647         }
648         if (mStateMachinesThread != null) {
649             mStateMachinesThread.quitSafely();
650             mStateMachinesThread = null;
651         }
652 
653         mHandler.removeCallbacksAndMessages(null);
654 
655         setBassClientService(null);
656         if (!leaudioBroadcastExtractPeriodicScannerFromStateMachine()) {
657             if (mSyncHandleToDeviceMap != null) {
658                 mSyncHandleToDeviceMap.clear();
659                 mSyncHandleToDeviceMap = null;
660             }
661             if (mPeriodicAdvertisementResultMap != null) {
662                 mPeriodicAdvertisementResultMap.clear();
663                 mPeriodicAdvertisementResultMap = null;
664             }
665             if (mActiveSourceMap != null) {
666                 mActiveSourceMap.clear();
667             }
668             if (mLocalBroadcastReceivers != null) {
669                 mLocalBroadcastReceivers.clear();
670             }
671             if (mPendingGroupOp != null) {
672                 mPendingGroupOp.clear();
673             }
674             if (mCachedBroadcasts != null) {
675                 mCachedBroadcasts.clear();
676             }
677             if (mBroadcastMetadataMap != null) {
678                 mBroadcastMetadataMap.clear();
679             }
680             if (mSyncHandleToBroadcastIdMap != null) {
681                 mSyncHandleToBroadcastIdMap.clear();
682                 mSyncHandleToBroadcastIdMap = null;
683             }
684             if (mSyncHandleToBaseDataMap != null) {
685                 mSyncHandleToBaseDataMap.clear();
686                 mSyncHandleToBaseDataMap = null;
687             }
688         } else {
689             synchronized (mSearchScanCallbackLock) {
690                 if (mBluetoothLeScannerWrapper != null && mSearchScanCallback != null) {
691                     mBluetoothLeScannerWrapper.stopScan(mSearchScanCallback);
692                 }
693                 mBluetoothLeScannerWrapper = null;
694                 mSearchScanCallback = null;
695                 clearAllSyncData();
696             }
697 
698             mLocalBroadcastReceivers.clear();
699             mPendingGroupOp.clear();
700             mBroadcastMetadataMap.clear();
701         }
702     }
703 
getDeviceForSyncHandle(int syncHandle)704     BluetoothDevice getDeviceForSyncHandle(int syncHandle) {
705         if (mSyncHandleToDeviceMap == null) {
706             return null;
707         }
708         return mSyncHandleToDeviceMap.get(syncHandle);
709     }
710 
getSyncHandleForBroadcastId(int broadcastId)711     int getSyncHandleForBroadcastId(int broadcastId) {
712         if (mSyncHandleToBroadcastIdMap == null) {
713             return BassConstants.INVALID_SYNC_HANDLE;
714         }
715 
716         int syncHandle = BassConstants.INVALID_SYNC_HANDLE;
717         for (Map.Entry<Integer, Integer> entry : mSyncHandleToBroadcastIdMap.entrySet()) {
718             Integer value = entry.getValue();
719             if (value == broadcastId) {
720                 syncHandle = entry.getKey();
721                 break;
722             }
723         }
724         return syncHandle;
725     }
726 
getBroadcastIdForSyncHandle(int syncHandle)727     int getBroadcastIdForSyncHandle(int syncHandle) {
728         if (mSyncHandleToBroadcastIdMap == null) {
729             return BassConstants.INVALID_BROADCAST_ID;
730         }
731 
732         if (mSyncHandleToBroadcastIdMap.containsKey(syncHandle)) {
733             return mSyncHandleToBroadcastIdMap.get(syncHandle);
734         }
735         return BassConstants.INVALID_BROADCAST_ID;
736     }
737 
updateSyncHandleForBroadcastId(int syncHandle, int broadcastId)738     void updateSyncHandleForBroadcastId(int syncHandle, int broadcastId) {
739         if (mSyncHandleToBroadcastIdMap == null) {
740             Log.e(TAG, "mSyncHandleToBroadcastIdMap is null");
741             return;
742         }
743 
744         mSyncHandleToBroadcastIdMap.entrySet().removeIf(entry -> entry.getValue() == broadcastId);
745         mSyncHandleToBroadcastIdMap.put(syncHandle, broadcastId);
746         log("Updated mSyncHandleToBroadcastIdMap: " + mSyncHandleToBroadcastIdMap);
747     }
748 
setBassClientService(BassClientService instance)749     private static synchronized void setBassClientService(BassClientService instance) {
750         Log.d(TAG, "setBassClientService(): set to: " + instance);
751         sService = instance;
752     }
753 
enqueueSourceGroupOp(BluetoothDevice sink, Integer msgId, Object obj)754     private void enqueueSourceGroupOp(BluetoothDevice sink, Integer msgId, Object obj) {
755         log("enqueueSourceGroupOp device: " + sink + ", msgId: " + msgId);
756 
757         if (!mPendingGroupOp.containsKey(sink)) {
758             mPendingGroupOp.put(sink, new ArrayList());
759         }
760         mPendingGroupOp.get(sink).add(new Pair<Integer, Object>(msgId, obj));
761     }
762 
isSuccess(int status)763     private boolean isSuccess(int status) {
764         boolean ret = false;
765         switch (status) {
766             case BluetoothStatusCodes.REASON_LOCAL_APP_REQUEST:
767             case BluetoothStatusCodes.REASON_LOCAL_STACK_REQUEST:
768             case BluetoothStatusCodes.REASON_REMOTE_REQUEST:
769             case BluetoothStatusCodes.REASON_SYSTEM_POLICY:
770                 ret = true;
771                 break;
772             default:
773                 break;
774         }
775         return ret;
776     }
777 
isAnyPendingAddSourceOperation()778     private boolean isAnyPendingAddSourceOperation() {
779         for (BluetoothDevice device : getConnectedDevices()) {
780             List<Pair<Integer, Object>> operations = mPendingGroupOp.get(device);
781             if (operations == null) {
782                 continue;
783             }
784 
785             boolean isAnyPendingAddSourceOperationForDevice =
786                     operations.stream()
787                             .anyMatch(e -> e.first.equals(BassClientStateMachine.ADD_BCAST_SOURCE));
788 
789             if (isAnyPendingAddSourceOperationForDevice) {
790                 return true;
791             }
792         }
793 
794         return false;
795     }
796 
checkForPendingGroupOpRequest( BluetoothDevice sink, int reason, int reqMsg, Object obj)797     private void checkForPendingGroupOpRequest(
798             BluetoothDevice sink, int reason, int reqMsg, Object obj) {
799         log(
800                 "checkForPendingGroupOpRequest device: "
801                         + sink
802                         + ", reason: "
803                         + reason
804                         + ", reqMsg: "
805                         + reqMsg);
806 
807         List<Pair<Integer, Object>> operations = mPendingGroupOp.get(sink);
808         if (operations == null) {
809             return;
810         }
811 
812         switch (reqMsg) {
813             case BassClientStateMachine.ADD_BCAST_SOURCE:
814                 if (obj == null) {
815                     return;
816                 }
817                 // Identify the operation by operation type and broadcastId
818                 if (isSuccess(reason)) {
819                     BluetoothLeBroadcastReceiveState sourceState =
820                             (BluetoothLeBroadcastReceiveState) obj;
821                     boolean removed =
822                             operations.removeIf(
823                                     m ->
824                                             (m.first.equals(
825                                                             BassClientStateMachine
826                                                                     .ADD_BCAST_SOURCE))
827                                                     && (sourceState.getBroadcastId()
828                                                             == ((BluetoothLeBroadcastMetadata)
829                                                                             m.second)
830                                                                     .getBroadcastId()));
831                     if (removed) {
832                         setSourceGroupManaged(sink, sourceState.getSourceId(), true);
833                     }
834                 } else {
835                     BluetoothLeBroadcastMetadata metadata = (BluetoothLeBroadcastMetadata) obj;
836                     operations.removeIf(
837                             m ->
838                                     (m.first.equals(BassClientStateMachine.ADD_BCAST_SOURCE))
839                                             && (metadata.getBroadcastId()
840                                                     == ((BluetoothLeBroadcastMetadata) m.second)
841                                                             .getBroadcastId()));
842 
843                     if (!isAnyPendingAddSourceOperation()
844                             && mIsAssistantActive
845                             && mPausedBroadcastSinks.isEmpty()) {
846                         LeAudioService leAudioService = mServiceFactory.getLeAudioService();
847                         mIsAssistantActive = false;
848                         mUnicastSourceStreamStatus = Optional.empty();
849 
850                         if (leAudioService != null) {
851                             leAudioService.activeBroadcastAssistantNotification(false);
852                         }
853                     }
854                 }
855                 break;
856             case BassClientStateMachine.REMOVE_BCAST_SOURCE:
857                 // Identify the operation by operation type and sourceId
858                 Integer sourceId = (Integer) obj;
859                 operations.removeIf(
860                         m ->
861                                 m.first.equals(BassClientStateMachine.REMOVE_BCAST_SOURCE)
862                                         && (sourceId.equals((Integer) m.second)));
863                 setSourceGroupManaged(sink, sourceId, false);
864                 break;
865             default:
866                 break;
867         }
868     }
869 
isDevicePartOfActiveUnicastGroup(BluetoothDevice device)870     private boolean isDevicePartOfActiveUnicastGroup(BluetoothDevice device) {
871         LeAudioService leAudioService = mServiceFactory.getLeAudioService();
872         if (leAudioService == null) {
873             return false;
874         }
875 
876         return (leAudioService.getActiveGroupId() != LE_AUDIO_GROUP_ID_INVALID)
877                 && (leAudioService.getActiveDevices().contains(device));
878     }
879 
isAnyDeviceFromActiveUnicastGroupReceivingBroadcast()880     private boolean isAnyDeviceFromActiveUnicastGroupReceivingBroadcast() {
881         return getActiveBroadcastSinks().stream()
882                 .anyMatch(d -> isDevicePartOfActiveUnicastGroup(d));
883     }
884 
localNotifyReceiveStateChanged(BluetoothDevice sink)885     private void localNotifyReceiveStateChanged(BluetoothDevice sink) {
886         LeAudioService leAudioService = mServiceFactory.getLeAudioService();
887         if (leAudioService == null) {
888             return;
889         }
890 
891         boolean isAssistantActive =
892                 areReceiversReceivingOnlyExternalBroadcast(getConnectedDevices());
893 
894         if (isAssistantActive) {
895             /* Assistant become active */
896             if (!mIsAssistantActive) {
897                 mIsAssistantActive = true;
898                 leAudioService.activeBroadcastAssistantNotification(true);
899             }
900 
901             if (leaudioAllowedContextMask()) {
902                 /* Don't bother active group (external broadcaster scenario) with SOUND EFFECTS */
903                 if (!mIsAllowedContextOfActiveGroupModified
904                         && isDevicePartOfActiveUnicastGroup(sink)) {
905                     leAudioService.setActiveGroupAllowedContextMask(
906                             BluetoothLeAudio.CONTEXTS_ALL
907                                     & ~BluetoothLeAudio.CONTEXT_TYPE_SOUND_EFFECTS,
908                             BluetoothLeAudio.CONTEXTS_ALL);
909                     mIsAllowedContextOfActiveGroupModified = true;
910                 }
911             }
912         } else {
913             /* Assistant become inactive */
914             if (mIsAssistantActive && mPausedBroadcastSinks.isEmpty()) {
915                 mIsAssistantActive = false;
916                 mUnicastSourceStreamStatus = Optional.empty();
917                 leAudioService.activeBroadcastAssistantNotification(false);
918             }
919 
920             if (leaudioAllowedContextMask()) {
921                 /* Restore allowed context mask for active device */
922                 if (mIsAllowedContextOfActiveGroupModified) {
923                     if (!isAnyDeviceFromActiveUnicastGroupReceivingBroadcast()) {
924                         leAudioService.setActiveGroupAllowedContextMask(
925                                 BluetoothLeAudio.CONTEXTS_ALL, BluetoothLeAudio.CONTEXTS_ALL);
926                     }
927                     mIsAllowedContextOfActiveGroupModified = false;
928                 }
929             }
930         }
931     }
932 
localNotifySourceAdded( BluetoothDevice sink, BluetoothLeBroadcastReceiveState receiveState)933     private void localNotifySourceAdded(
934             BluetoothDevice sink, BluetoothLeBroadcastReceiveState receiveState) {
935         if (!isLocalBroadcast(receiveState)) {
936             return;
937         }
938 
939         int broadcastId = receiveState.getBroadcastId();
940 
941         /* Track devices bonded to local broadcast for further broadcast status handling when sink
942          * device is:
943          *     - disconnecting (if no more receivers, broadcast can be stopped)
944          *     - connecting (resynchronize if connection lost)
945          */
946         if (mLocalBroadcastReceivers.containsKey(broadcastId)) {
947             mLocalBroadcastReceivers.get(broadcastId).add(sink);
948         } else {
949             mLocalBroadcastReceivers.put(
950                     broadcastId, new HashSet<BluetoothDevice>(Arrays.asList(sink)));
951         }
952     }
953 
setSourceGroupManaged(BluetoothDevice sink, int sourceId, boolean isGroupOp)954     private void setSourceGroupManaged(BluetoothDevice sink, int sourceId, boolean isGroupOp) {
955         log("setSourceGroupManaged device: " + sink);
956         if (isGroupOp) {
957             if (!mGroupManagedSources.containsKey(sink)) {
958                 mGroupManagedSources.put(sink, new ArrayList<>());
959             }
960             mGroupManagedSources.get(sink).add(sourceId);
961         } else {
962             List<Integer> sources = mGroupManagedSources.get(sink);
963             if (sources != null) {
964                 sources.removeIf(e -> e.equals(sourceId));
965             }
966         }
967     }
968 
969     private Pair<BluetoothLeBroadcastMetadata, Map<BluetoothDevice, Integer>>
getGroupManagedDeviceSources(BluetoothDevice sink, Integer sourceId)970             getGroupManagedDeviceSources(BluetoothDevice sink, Integer sourceId) {
971         log("getGroupManagedDeviceSources device: " + sink + " sourceId: " + sourceId);
972         Map map = new HashMap<BluetoothDevice, Integer>();
973 
974         if (mGroupManagedSources.containsKey(sink)
975                 && mGroupManagedSources.get(sink).contains(sourceId)) {
976             BassClientStateMachine stateMachine = getOrCreateStateMachine(sink);
977             if (stateMachine == null) {
978                 Log.e(TAG, "Can't get state machine for device: " + sink);
979                 return new Pair<BluetoothLeBroadcastMetadata, Map<BluetoothDevice, Integer>>(
980                         null, null);
981             }
982 
983             BluetoothLeBroadcastMetadata metadata =
984                     stateMachine.getCurrentBroadcastMetadata(sourceId);
985             if (metadata != null) {
986                 int broadcastId = metadata.getBroadcastId();
987 
988                 for (BluetoothDevice device : getTargetDeviceList(sink, true)) {
989                     List<BluetoothLeBroadcastReceiveState> sources =
990                             getOrCreateStateMachine(device).getAllSources();
991 
992                     // For each device, find the source ID having this broadcast ID
993                     Optional<BluetoothLeBroadcastReceiveState> receiver =
994                             sources.stream()
995                                     .filter(e -> e.getBroadcastId() == broadcastId)
996                                     .findAny();
997                     if (receiver.isPresent()) {
998                         map.put(device, receiver.get().getSourceId());
999                     } else {
1000                         // Put invalid source ID if the remote doesn't have it
1001                         map.put(device, BassConstants.INVALID_SOURCE_ID);
1002                     }
1003                 }
1004                 return new Pair<BluetoothLeBroadcastMetadata, Map<BluetoothDevice, Integer>>(
1005                         metadata, map);
1006             } else {
1007                 Log.e(
1008                         TAG,
1009                         "Couldn't find broadcast metadata for device: "
1010                                 + sink.getAnonymizedAddress()
1011                                 + ", and sourceId:"
1012                                 + sourceId);
1013             }
1014         }
1015 
1016         // Just put this single device if this source is not group managed
1017         map.put(sink, sourceId);
1018         return new Pair<BluetoothLeBroadcastMetadata, Map<BluetoothDevice, Integer>>(null, map);
1019     }
1020 
getTargetDeviceList(BluetoothDevice device, boolean isGroupOp)1021     private List<BluetoothDevice> getTargetDeviceList(BluetoothDevice device, boolean isGroupOp) {
1022         if (isGroupOp) {
1023             CsipSetCoordinatorService csipClient = mServiceFactory.getCsipSetCoordinatorService();
1024             if (csipClient != null) {
1025                 // Check for coordinated set of devices in the context of CAP
1026                 List<BluetoothDevice> csipDevices =
1027                         csipClient.getGroupDevicesOrdered(device, BluetoothUuid.CAP);
1028                 if (!csipDevices.isEmpty()) {
1029                     return csipDevices;
1030                 } else {
1031                     Log.w(TAG, "CSIP group is empty.");
1032                 }
1033             } else {
1034                 Log.e(TAG, "CSIP service is null. No grouping information available.");
1035             }
1036         }
1037 
1038         List<BluetoothDevice> devices = new ArrayList<>();
1039         devices.add(device);
1040         return devices;
1041     }
1042 
isValidBroadcastSourceAddition( BluetoothDevice device, BluetoothLeBroadcastMetadata metaData)1043     private boolean isValidBroadcastSourceAddition(
1044             BluetoothDevice device, BluetoothLeBroadcastMetadata metaData) {
1045         boolean retval = true;
1046         List<BluetoothLeBroadcastReceiveState> currentAllSources = getAllSources(device);
1047         for (int i = 0; i < currentAllSources.size(); i++) {
1048             BluetoothLeBroadcastReceiveState state = currentAllSources.get(i);
1049             if (metaData.getSourceDevice().equals(state.getSourceDevice())
1050                     && metaData.getSourceAddressType() == state.getSourceAddressType()
1051                     && metaData.getSourceAdvertisingSid() == state.getSourceAdvertisingSid()
1052                     && metaData.getBroadcastId() == state.getBroadcastId()) {
1053                 retval = false;
1054                 Log.e(
1055                         TAG,
1056                         "isValidBroadcastSourceAddition: fail for "
1057                                 + device
1058                                 + " metaData: "
1059                                 + metaData);
1060                 break;
1061             }
1062         }
1063         return retval;
1064     }
1065 
hasRoomForBroadcastSourceAddition(BluetoothDevice device)1066     private boolean hasRoomForBroadcastSourceAddition(BluetoothDevice device) {
1067         BassClientStateMachine stateMachine = null;
1068         synchronized (mStateMachines) {
1069             stateMachine = getOrCreateStateMachine(device);
1070         }
1071         if (stateMachine == null) {
1072             log("stateMachine is null");
1073             return false;
1074         }
1075         boolean isRoomAvailable = false;
1076         String emptyBluetoothDevice = "00:00:00:00:00:00";
1077         for (BluetoothLeBroadcastReceiveState recvState : stateMachine.getAllSources()) {
1078             if (recvState.getSourceDevice().getAddress().equals(emptyBluetoothDevice)) {
1079                 isRoomAvailable = true;
1080                 break;
1081             }
1082         }
1083         log("isRoomAvailable: " + isRoomAvailable);
1084         return isRoomAvailable;
1085     }
1086 
getSourceIdToRemove(BluetoothDevice device)1087     private Integer getSourceIdToRemove(BluetoothDevice device) {
1088         BassClientStateMachine stateMachine = null;
1089 
1090         synchronized (mStateMachines) {
1091             stateMachine = getOrCreateStateMachine(device);
1092         }
1093         if (stateMachine == null) {
1094             log("stateMachine is null");
1095             return BassConstants.INVALID_SOURCE_ID;
1096         }
1097         List<BluetoothLeBroadcastReceiveState> sources = stateMachine.getAllSources();
1098         if (sources.isEmpty()) {
1099             log("sources is empty");
1100             return BassConstants.INVALID_SOURCE_ID;
1101         }
1102 
1103         Integer sourceId = BassConstants.INVALID_SOURCE_ID;
1104         // Select the source by checking if there is one with PA not synced
1105         Optional<BluetoothLeBroadcastReceiveState> receiver =
1106                 sources.stream()
1107                         .filter(
1108                                 e ->
1109                                         (e.getPaSyncState()
1110                                                 != BluetoothLeBroadcastReceiveState
1111                                                         .PA_SYNC_STATE_SYNCHRONIZED))
1112                         .findAny();
1113         if (receiver.isPresent()) {
1114             sourceId = receiver.get().getSourceId();
1115         } else {
1116             // If all sources are synced, continue to pick the 1st source
1117             sourceId = sources.get(0).getSourceId();
1118         }
1119         return sourceId;
1120     }
1121 
getOrCreateStateMachine(BluetoothDevice device)1122     private BassClientStateMachine getOrCreateStateMachine(BluetoothDevice device) {
1123         if (device == null) {
1124             Log.e(TAG, "getOrCreateStateMachine failed: device cannot be null");
1125             return null;
1126         }
1127         synchronized (mStateMachines) {
1128             BassClientStateMachine stateMachine = mStateMachines.get(device);
1129             if (stateMachine != null) {
1130                 return stateMachine;
1131             }
1132             // Limit the maximum number of state machines to avoid DoS attack
1133             if (mStateMachines.size() >= MAX_BASS_CLIENT_STATE_MACHINES) {
1134                 Log.e(
1135                         TAG,
1136                         "Maximum number of Bassclient state machines reached: "
1137                                 + MAX_BASS_CLIENT_STATE_MACHINES);
1138                 return null;
1139             }
1140             log("Creating a new state machine for " + device);
1141             stateMachine =
1142                     BassObjectsFactory.getInstance()
1143                             .makeStateMachine(
1144                                     device,
1145                                     this,
1146                                     mAdapterService,
1147                                     mStateMachinesThread.getLooper());
1148             if (stateMachine != null) {
1149                 mStateMachines.put(device, stateMachine);
1150             }
1151 
1152             return stateMachine;
1153         }
1154     }
1155 
1156     class DialingOutTimeoutEvent implements Runnable {
1157         Integer mBroadcastId;
1158 
DialingOutTimeoutEvent(Integer broadcastId)1159         DialingOutTimeoutEvent(Integer broadcastId) {
1160             mBroadcastId = broadcastId;
1161         }
1162 
1163         @Override
run()1164         public void run() {
1165             mDialingOutTimeoutEvent = null;
1166 
1167             if (getBassClientService() == null) {
1168                 Log.e(TAG, "DialingOutTimeoutEvent: No Bass service");
1169                 return;
1170             }
1171 
1172             LeAudioService leAudioService = mServiceFactory.getLeAudioService();
1173             if (leAudioService == null) {
1174                 Log.d(TAG, "DialingOutTimeoutEvent: No available LeAudioService");
1175                 return;
1176             }
1177 
1178             sEventLogger.logd(TAG, "Broadcast timeout: " + mBroadcastId);
1179             mLocalBroadcastReceivers.remove(mBroadcastId);
1180             leAudioService.stopBroadcast(mBroadcastId);
1181         }
1182 
isScheduledForBroadcast(Integer broadcastId)1183         public boolean isScheduledForBroadcast(Integer broadcastId) {
1184             return mBroadcastId.equals(broadcastId);
1185         }
1186     }
1187 
1188     /**
1189      * Get the BassClientService instance
1190      *
1191      * @return BassClientService instance
1192      */
getBassClientService()1193     public static synchronized BassClientService getBassClientService() {
1194         if (sService == null) {
1195             Log.w(TAG, "getBassClientService(): service is NULL");
1196             return null;
1197         }
1198         if (!sService.isAvailable()) {
1199             Log.w(TAG, "getBassClientService(): service is not available");
1200             return null;
1201         }
1202         return sService;
1203     }
1204 
removeStateMachine(BluetoothDevice device)1205     private void removeStateMachine(BluetoothDevice device) {
1206         synchronized (mStateMachines) {
1207             BassClientStateMachine sm = mStateMachines.get(device);
1208             if (sm == null) {
1209                 Log.w(
1210                         TAG,
1211                         "removeStateMachine: device " + device + " does not have a state machine");
1212                 return;
1213             }
1214             log("removeStateMachine: removing state machine for device: " + device);
1215             sm.doQuit();
1216             sm.cleanup();
1217             mStateMachines.remove(device);
1218         }
1219 
1220         // Cleanup device cache
1221         mPendingGroupOp.remove(device);
1222         mGroupManagedSources.remove(device);
1223         mActiveSourceMap.remove(device);
1224     }
1225 
handleReconnectingAudioSharingModeDevice(BluetoothDevice device)1226     private void handleReconnectingAudioSharingModeDevice(BluetoothDevice device) {
1227         /* In case of reconnecting Audio Sharing mode device */
1228         if (mDialingOutTimeoutEvent != null) {
1229             for (Map.Entry<Integer, HashSet<BluetoothDevice>> entry :
1230                     mLocalBroadcastReceivers.entrySet()) {
1231                 Integer broadcastId = entry.getKey();
1232                 HashSet<BluetoothDevice> devices = entry.getValue();
1233 
1234                 /* If associated with any broadcast, try to remove pending timeout callback */
1235                 if ((mDialingOutTimeoutEvent.isScheduledForBroadcast(broadcastId))
1236                         && (devices.contains(device))) {
1237                     Log.i(
1238                             TAG,
1239                             "connectionStateChanged: reconnected previousely synced device: "
1240                                     + device);
1241                     mHandler.removeCallbacks(mDialingOutTimeoutEvent);
1242                     mDialingOutTimeoutEvent = null;
1243                     break;
1244                 }
1245             }
1246         }
1247     }
1248 
informConnectedDeviceAboutScanOffloadStop()1249     private void informConnectedDeviceAboutScanOffloadStop() {
1250         for (BluetoothDevice device : getConnectedDevices()) {
1251             synchronized (mStateMachines) {
1252                 BassClientStateMachine stateMachine = getOrCreateStateMachine(device);
1253                 if (stateMachine == null) {
1254                     Log.w(
1255                             TAG,
1256                             "informConnectedDeviceAboutScanOffloadStop: Can't get state "
1257                                     + "machine for device: "
1258                                     + device);
1259                     continue;
1260                 }
1261                 stateMachine.sendMessage(BassClientStateMachine.STOP_SCAN_OFFLOAD);
1262             }
1263         }
1264     }
1265 
areValidParametersToModifySource( BluetoothLeBroadcastMetadata updatedMetadata, BassClientStateMachine stateMachine, Integer deviceSourceId, BluetoothDevice device)1266     private int areValidParametersToModifySource(
1267             BluetoothLeBroadcastMetadata updatedMetadata,
1268             BassClientStateMachine stateMachine,
1269             Integer deviceSourceId,
1270             BluetoothDevice device) {
1271         if (updatedMetadata == null || stateMachine == null) {
1272             log(
1273                     "areValidParametersToModifySource: Error bad parameters: sourceId = "
1274                             + deviceSourceId
1275                             + " updatedMetadata = "
1276                             + updatedMetadata);
1277             return BluetoothStatusCodes.ERROR_BAD_PARAMETERS;
1278         }
1279         if (deviceSourceId == BassConstants.INVALID_SOURCE_ID) {
1280             log("areValidParametersToModifySource: no such sourceId for device: " + device);
1281             return BluetoothStatusCodes.ERROR_LE_BROADCAST_ASSISTANT_INVALID_SOURCE_ID;
1282         }
1283         if (getConnectionState(device) != BluetoothProfile.STATE_CONNECTED) {
1284             log("areValidParametersToModifySource: device is not connected");
1285             return BluetoothStatusCodes.ERROR_REMOTE_LINK_ERROR;
1286         }
1287         byte[] code = updatedMetadata.getBroadcastCode();
1288         if ((code != null) && (code.length != 0)) {
1289             if ((code.length > 16) || (code.length < 4)) {
1290                 log(
1291                         "areValidParametersToModifySource: Invalid broadcast code length: "
1292                                 + code.length
1293                                 + ", should be between 4 and 16 octets");
1294                 return BluetoothStatusCodes.ERROR_BAD_PARAMETERS;
1295             }
1296         }
1297         if (stateMachine.hasPendingSourceOperation()) {
1298             Log.w(
1299                     TAG,
1300                     "modifySource: source operation already pending, device: "
1301                             + device
1302                             + ", broadcastId: "
1303                             + updatedMetadata.getBroadcastId());
1304             return BluetoothStatusCodes.ERROR_ALREADY_IN_TARGET_STATE;
1305         }
1306 
1307         return BluetoothStatusCodes.SUCCESS;
1308     }
1309 
handleConnectionStateChanged(BluetoothDevice device, int fromState, int toState)1310     void handleConnectionStateChanged(BluetoothDevice device, int fromState, int toState) {
1311         mHandler.post(() -> connectionStateChanged(device, fromState, toState));
1312     }
1313 
connectionStateChanged(BluetoothDevice device, int fromState, int toState)1314     synchronized void connectionStateChanged(BluetoothDevice device, int fromState, int toState) {
1315         if (!isAvailable()) {
1316             Log.w(TAG, "connectionStateChanged: service is not available");
1317             return;
1318         }
1319 
1320         if ((device == null) || (fromState == toState)) {
1321             Log.e(
1322                     TAG,
1323                     "connectionStateChanged: unexpected invocation. device="
1324                             + device
1325                             + " fromState="
1326                             + fromState
1327                             + " toState="
1328                             + toState);
1329             return;
1330         }
1331 
1332         sEventLogger.logd(
1333                 TAG,
1334                 "connectionStateChanged: device: "
1335                         + device
1336                         + ", fromState= "
1337                         + BluetoothProfile.getConnectionStateName(fromState)
1338                         + ", toState= "
1339                         + BluetoothProfile.getConnectionStateName(toState));
1340 
1341         // Check if the device is disconnected - if unbond, remove the state machine
1342         if (toState == BluetoothProfile.STATE_DISCONNECTED) {
1343             mPendingGroupOp.remove(device);
1344 
1345             int bondState = mAdapterService.getBondState(device);
1346             if (bondState == BluetoothDevice.BOND_NONE) {
1347                 log("Unbonded " + device + ". Removing state machine");
1348                 removeStateMachine(device);
1349             }
1350         } else if (toState == BluetoothProfile.STATE_CONNECTED) {
1351             handleReconnectingAudioSharingModeDevice(device);
1352         }
1353     }
1354 
handleBondStateChanged(BluetoothDevice device, int fromState, int toState)1355     public void handleBondStateChanged(BluetoothDevice device, int fromState, int toState) {
1356         mHandler.post(() -> bondStateChanged(device, toState));
1357     }
1358 
1359     @VisibleForTesting
bondStateChanged(BluetoothDevice device, int bondState)1360     void bondStateChanged(BluetoothDevice device, int bondState) {
1361         log("Bond state changed for device: " + device + " state: " + bondState);
1362 
1363         // Remove state machine if the bonding for a device is removed
1364         if (bondState != BluetoothDevice.BOND_NONE) {
1365             return;
1366         }
1367 
1368         synchronized (mStateMachines) {
1369             BassClientStateMachine sm = mStateMachines.get(device);
1370             if (sm == null) {
1371                 return;
1372             }
1373             if (sm.getConnectionState() != BluetoothProfile.STATE_DISCONNECTED) {
1374                 Log.i(TAG, "Disconnecting device because it was unbonded.");
1375                 disconnect(device);
1376                 return;
1377             }
1378             removeStateMachine(device);
1379         }
1380     }
1381 
1382     /**
1383      * Connects the bass profile to the passed in device
1384      *
1385      * @param device is the device with which we will connect the Bass profile
1386      * @return true if BAss profile successfully connected, false otherwise
1387      */
connect(BluetoothDevice device)1388     public boolean connect(BluetoothDevice device) {
1389         Log.d(TAG, "connect(): " + device);
1390         if (device == null) {
1391             Log.e(TAG, "connect: device is null");
1392             return false;
1393         }
1394         if (getConnectionPolicy(device) == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
1395             Log.e(TAG, "connect: connection policy set to forbidden");
1396             return false;
1397         }
1398         synchronized (mStateMachines) {
1399             BassClientStateMachine stateMachine = getOrCreateStateMachine(device);
1400             if (stateMachine == null) {
1401                 Log.e(TAG, "Can't get state machine for device: " + device);
1402                 return false;
1403             }
1404 
1405             stateMachine.sendMessage(BassClientStateMachine.CONNECT);
1406         }
1407         return true;
1408     }
1409 
1410     /**
1411      * Disconnects Bassclient profile for the passed in device
1412      *
1413      * @param device is the device with which we want to disconnected the BAss client profile
1414      * @return true if Bass client profile successfully disconnected, false otherwise
1415      */
disconnect(BluetoothDevice device)1416     public boolean disconnect(BluetoothDevice device) {
1417         Log.d(TAG, "disconnect(): " + device);
1418         if (device == null) {
1419             Log.e(TAG, "disconnect: device is null");
1420             return false;
1421         }
1422         synchronized (mStateMachines) {
1423             BassClientStateMachine stateMachine = getOrCreateStateMachine(device);
1424             if (stateMachine == null) {
1425                 Log.e(TAG, "Can't get state machine for device: " + device);
1426                 return false;
1427             }
1428 
1429             stateMachine.sendMessage(BassClientStateMachine.DISCONNECT);
1430         }
1431         return true;
1432     }
1433 
1434     /**
1435      * Check whether can connect to a peer device. The check considers a number of factors during
1436      * the evaluation.
1437      *
1438      * @param device the peer device to connect to
1439      * @return true if connection is allowed, otherwise false
1440      */
1441     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
okToConnect(BluetoothDevice device)1442     public boolean okToConnect(BluetoothDevice device) {
1443         // Check if this is an incoming connection in Quiet mode.
1444         if (mAdapterService.isQuietModeEnabled()) {
1445             Log.e(TAG, "okToConnect: cannot connect to " + device + " : quiet mode enabled");
1446             return false;
1447         }
1448         // Check connection policy and accept or reject the connection.
1449         int connectionPolicy = getConnectionPolicy(device);
1450         int bondState = mAdapterService.getBondState(device);
1451         // Allow this connection only if the device is bonded. Any attempt to connect while
1452         // bonding would potentially lead to an unauthorized connection.
1453         if (bondState != BluetoothDevice.BOND_BONDED) {
1454             Log.w(TAG, "okToConnect: return false, bondState=" + bondState);
1455             return false;
1456         } else if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_UNKNOWN
1457                 && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
1458             // Otherwise, reject the connection if connectionPolicy is not valid.
1459             Log.w(TAG, "okToConnect: return false, connectionPolicy=" + connectionPolicy);
1460             return false;
1461         }
1462         return true;
1463     }
1464 
1465     /**
1466      * Get connection state of remote device
1467      *
1468      * @param sink the remote device
1469      * @return connection state
1470      */
getConnectionState(BluetoothDevice sink)1471     public int getConnectionState(BluetoothDevice sink) {
1472         synchronized (mStateMachines) {
1473             BassClientStateMachine sm = getOrCreateStateMachine(sink);
1474             if (sm == null) {
1475                 log("getConnectionState returns STATE_DISC");
1476                 return BluetoothProfile.STATE_DISCONNECTED;
1477             }
1478             return sm.getConnectionState();
1479         }
1480     }
1481 
1482     /**
1483      * Get a list of all LE Audio Broadcast Sinks with the specified connection states.
1484      *
1485      * @param states states array representing the connection states
1486      * @return a list of devices that match the provided connection states
1487      */
getDevicesMatchingConnectionStates(int[] states)1488     List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
1489         ArrayList<BluetoothDevice> devices = new ArrayList<>();
1490         if (states == null) {
1491             return devices;
1492         }
1493         final BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices();
1494         if (bondedDevices == null) {
1495             return devices;
1496         }
1497         synchronized (mStateMachines) {
1498             for (BluetoothDevice device : bondedDevices) {
1499                 final ParcelUuid[] featureUuids = device.getUuids();
1500                 if (!Utils.arrayContains(featureUuids, BluetoothUuid.BASS)) {
1501                     continue;
1502                 }
1503                 int connectionState = BluetoothProfile.STATE_DISCONNECTED;
1504                 BassClientStateMachine sm = getOrCreateStateMachine(device);
1505                 if (sm != null) {
1506                     connectionState = sm.getConnectionState();
1507                 }
1508                 for (int state : states) {
1509                     if (connectionState == state) {
1510                         devices.add(device);
1511                         break;
1512                     }
1513                 }
1514             }
1515             return devices;
1516         }
1517     }
1518 
1519     /**
1520      * Get a list of all LE Audio Broadcast Sinks connected with the LE Audio Broadcast Assistant.
1521      *
1522      * @return list of connected devices
1523      */
getConnectedDevices()1524     public List<BluetoothDevice> getConnectedDevices() {
1525         synchronized (mStateMachines) {
1526             List<BluetoothDevice> devices = new ArrayList<>();
1527             for (BassClientStateMachine sm : mStateMachines.values()) {
1528                 if (sm.isConnected()) {
1529                     devices.add(sm.getDevice());
1530                 }
1531             }
1532             log("getConnectedDevices: " + devices);
1533             return devices;
1534         }
1535     }
1536 
1537     /**
1538      * Set the connectionPolicy of the Broadcast Audio Scan Service profile.
1539      *
1540      * <p>The connection policy can be one of: {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
1541      * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}, {@link
1542      * BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
1543      *
1544      * @param device paired bluetooth device
1545      * @param connectionPolicy is the connection policy to set to for this profile
1546      * @return true if connectionPolicy is set, false on error
1547      */
setConnectionPolicy(BluetoothDevice device, int connectionPolicy)1548     public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
1549         Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy);
1550         boolean setSuccessfully =
1551                 mDatabaseManager.setProfileConnectionPolicy(
1552                         device, BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT, connectionPolicy);
1553         if (setSuccessfully && connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
1554             connect(device);
1555         } else if (setSuccessfully
1556                 && connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
1557             disconnect(device);
1558         }
1559         return setSuccessfully;
1560     }
1561 
1562     /**
1563      * Get the connection policy of the profile.
1564      *
1565      * <p>The connection policy can be any of: {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
1566      * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}, {@link
1567      * BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
1568      *
1569      * @param device paired bluetooth device
1570      * @return connection policy of the device
1571      */
getConnectionPolicy(BluetoothDevice device)1572     public int getConnectionPolicy(BluetoothDevice device) {
1573         return mDatabaseManager.getProfileConnectionPolicy(
1574                 device, BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
1575     }
1576 
1577     /**
1578      * Register callbacks that will be invoked during scan offloading.
1579      *
1580      * @param cb callbacks to be invoked
1581      */
registerCallback(IBluetoothLeBroadcastAssistantCallback cb)1582     public void registerCallback(IBluetoothLeBroadcastAssistantCallback cb) {
1583         Log.i(TAG, "registerCallback");
1584         mCallbacks.register(cb);
1585     }
1586 
1587     /**
1588      * Unregister callbacks that are invoked during scan offloading.
1589      *
1590      * @param cb callbacks to be unregistered
1591      */
unregisterCallback(IBluetoothLeBroadcastAssistantCallback cb)1592     public void unregisterCallback(IBluetoothLeBroadcastAssistantCallback cb) {
1593         Log.i(TAG, "unregisterCallback");
1594         mCallbacks.unregister(cb);
1595     }
1596 
1597     /**
1598      * Search for LE Audio Broadcast Sources on behalf of all devices connected via Broadcast Audio
1599      * Scan Service, filtered by filters
1600      *
1601      * @param filters ScanFilters for finding exact Broadcast Source
1602      */
startSearchingForSources(List<ScanFilter> filters)1603     public void startSearchingForSources(List<ScanFilter> filters) {
1604         log("startSearchingForSources");
1605         if (mBluetoothAdapter == null) {
1606             Log.e(TAG, "startSearchingForSources: Adapter is NULL");
1607             mCallbacks.notifySearchStartFailed(BluetoothStatusCodes.ERROR_UNKNOWN);
1608             return;
1609         }
1610 
1611         if (!BluetoothMethodProxy.getInstance()
1612                 .initializePeriodicAdvertisingManagerOnDefaultAdapter()) {
1613             Log.e(TAG, "Failed to initialize Periodic Advertising Manager on Default Adapter");
1614             mCallbacks.notifySearchStartFailed(BluetoothStatusCodes.ERROR_UNKNOWN);
1615             return;
1616         }
1617 
1618         synchronized (mSearchScanCallbackLock) {
1619             if (!leaudioBroadcastExtractPeriodicScannerFromStateMachine()
1620                     || mBluetoothLeScannerWrapper == null) {
1621                 mBluetoothLeScannerWrapper =
1622                         BassObjectsFactory.getInstance()
1623                                 .getBluetoothLeScannerWrapper(mBluetoothAdapter);
1624             }
1625             if (mBluetoothLeScannerWrapper == null) {
1626                 Log.e(TAG, "startLeScan: cannot get BluetoothLeScanner");
1627                 mCallbacks.notifySearchStartFailed(BluetoothStatusCodes.ERROR_UNKNOWN);
1628                 return;
1629             }
1630             if (mSearchScanCallback != null) {
1631                 Log.e(TAG, "LE Scan has already started");
1632                 mCallbacks.notifySearchStartFailed(BluetoothStatusCodes.ERROR_UNKNOWN);
1633                 return;
1634             }
1635             mSearchScanCallback =
1636                     new ScanCallback() {
1637                         @Override
1638                         public void onScanResult(int callbackType, ScanResult result) {
1639                             log("onScanResult:" + result);
1640                             synchronized (mSearchScanCallbackLock) {
1641                                 // check mSearchScanCallback because even after
1642                                 // mBluetoothLeScannerWrapper.stopScan(mSearchScanCallback) that
1643                                 // callback could be called
1644                                 if (mSearchScanCallback == null) {
1645                                     log("onScanResult: scanner already stopped");
1646                                     return;
1647                                 }
1648                                 if (callbackType != ScanSettings.CALLBACK_TYPE_ALL_MATCHES) {
1649                                     // Should not happen
1650                                     Log.e(TAG, "LE Scan has already started");
1651                                     return;
1652                                 }
1653                                 ScanRecord scanRecord = result.getScanRecord();
1654                                 if (scanRecord == null) {
1655                                     Log.e(TAG, "Null scan record");
1656                                     return;
1657                                 }
1658                                 Map<ParcelUuid, byte[]> listOfUuids = scanRecord.getServiceData();
1659                                 if (listOfUuids == null) {
1660                                     Log.e(TAG, "Service data is null");
1661                                     return;
1662                                 }
1663                                 if (!listOfUuids.containsKey(BassConstants.BAAS_UUID)) {
1664                                     return;
1665                                 }
1666                                 log("Broadcast Source Found:" + result.getDevice());
1667                                 byte[] broadcastIdArray = listOfUuids.get(BassConstants.BAAS_UUID);
1668                                 int broadcastId =
1669                                         (int)
1670                                                 (((broadcastIdArray[2] & 0xff) << 16)
1671                                                         | ((broadcastIdArray[1] & 0xff) << 8)
1672                                                         | (broadcastIdArray[0] & 0xff));
1673 
1674                                 sEventLogger.logd(
1675                                         TAG,
1676                                         "Broadcast Source Found: Broadcast ID: " + broadcastId);
1677 
1678                                 if (broadcastId != BassConstants.INVALID_BROADCAST_ID
1679                                         && mCachedBroadcasts.get(broadcastId) == null) {
1680                                     log("selectBroadcastSource: broadcastId " + broadcastId);
1681                                     mCachedBroadcasts.put(broadcastId, result);
1682                                     if (leaudioBroadcastExtractPeriodicScannerFromStateMachine()) {
1683                                         addSelectSourceRequest(result, false);
1684                                     } else {
1685                                         synchronized (mStateMachines) {
1686                                             for (BassClientStateMachine sm :
1687                                                     mStateMachines.values()) {
1688                                                 if (sm.isConnected()) {
1689                                                     selectSource(sm.getDevice(), result, false);
1690                                                 }
1691                                             }
1692                                         }
1693                                     }
1694                                 }
1695                             }
1696                         }
1697 
1698                         public void onScanFailed(int errorCode) {
1699                             Log.e(TAG, "Scan Failure:" + errorCode);
1700                             informConnectedDeviceAboutScanOffloadStop();
1701                         }
1702                     };
1703             mHandler.removeMessages(MESSAGE_SYNC_TIMEOUT);
1704             // when starting scan, clear the previously cached broadcast scan results
1705             mCachedBroadcasts.clear();
1706             // clear previous sources notify flag before scanning new result
1707             // this is to make sure the active sources are notified even if already synced
1708             if (mPeriodicAdvertisementResultMap != null) {
1709                 clearNotifiedFlags();
1710             }
1711             ScanSettings settings =
1712                     new ScanSettings.Builder()
1713                             .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
1714                             .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
1715                             .setLegacy(false)
1716                             .build();
1717             if (filters == null) {
1718                 filters = new ArrayList<ScanFilter>();
1719             }
1720             if (!BassUtils.containUuid(filters, BassConstants.BAAS_UUID)) {
1721                 byte[] serviceData = {0x00, 0x00, 0x00}; // Broadcast_ID
1722                 byte[] serviceDataMask = {0x00, 0x00, 0x00};
1723 
1724                 filters.add(
1725                         new ScanFilter.Builder()
1726                                 .setServiceData(
1727                                         BassConstants.BAAS_UUID, serviceData, serviceDataMask)
1728                                 .build());
1729             }
1730 
1731             for (BluetoothDevice device : getConnectedDevices()) {
1732                 synchronized (mStateMachines) {
1733                     BassClientStateMachine stateMachine = getOrCreateStateMachine(device);
1734                     if (stateMachine == null) {
1735                         Log.w(
1736                                 TAG,
1737                                 "startSearchingForSources: Can't get state machine for "
1738                                         + "device: "
1739                                         + device);
1740                         continue;
1741                     }
1742                     stateMachine.sendMessage(BassClientStateMachine.START_SCAN_OFFLOAD);
1743                 }
1744             }
1745 
1746             mBluetoothLeScannerWrapper.startScan(filters, settings, mSearchScanCallback);
1747             sEventLogger.logd(TAG, "startSearchingForSources");
1748             mCallbacks.notifySearchStarted(BluetoothStatusCodes.REASON_LOCAL_APP_REQUEST);
1749         }
1750     }
1751 
1752     /** Stops an ongoing search for nearby Broadcast Sources */
stopSearchingForSources()1753     public void stopSearchingForSources() {
1754         log("stopSearchingForSources");
1755         if (mBluetoothAdapter == null) {
1756             Log.e(TAG, "stopSearchingForSources: Adapter is NULL");
1757             return;
1758         }
1759         if (!leaudioBroadcastExtractPeriodicScannerFromStateMachine()) {
1760             BluetoothLeScannerWrapper scanner =
1761                     BassObjectsFactory.getInstance()
1762                             .getBluetoothLeScannerWrapper(mBluetoothAdapter);
1763             if (scanner == null) {
1764                 Log.e(TAG, "startLeScan: cannot get BluetoothLeScanner");
1765                 return;
1766             }
1767             synchronized (mSearchScanCallbackLock) {
1768                 if (mSearchScanCallback == null) {
1769                     Log.e(TAG, "Scan not started yet");
1770                     mCallbacks.notifySearchStopFailed(BluetoothStatusCodes.ERROR_UNKNOWN);
1771                     return;
1772                 }
1773                 informConnectedDeviceAboutScanOffloadStop();
1774                 scanner.stopScan(mSearchScanCallback);
1775                 mSearchScanCallback = null;
1776                 sEventLogger.logd(TAG, "stopSearchingForSources");
1777                 mCallbacks.notifySearchStopped(BluetoothStatusCodes.REASON_LOCAL_APP_REQUEST);
1778             }
1779         } else {
1780             synchronized (mSearchScanCallbackLock) {
1781                 if (mBluetoothLeScannerWrapper == null || mSearchScanCallback == null) {
1782                     Log.e(TAG, "Scan not started yet");
1783                     mCallbacks.notifySearchStopFailed(BluetoothStatusCodes.ERROR_UNKNOWN);
1784                     return;
1785                 }
1786                 mBluetoothLeScannerWrapper.stopScan(mSearchScanCallback);
1787                 mBluetoothLeScannerWrapper = null;
1788                 mSearchScanCallback = null;
1789                 clearAllSyncData();
1790                 informConnectedDeviceAboutScanOffloadStop();
1791                 sEventLogger.logd(TAG, "stopSearchingForSources");
1792                 mCallbacks.notifySearchStopped(BluetoothStatusCodes.REASON_LOCAL_APP_REQUEST);
1793             }
1794         }
1795     }
1796 
clearAllSyncData()1797     private void clearAllSyncData() {
1798         mSourceSyncRequestsQueue.clear();
1799         mPendingSourcesToAdd.clear();
1800 
1801         cancelActiveSync(null);
1802         mActiveSyncedSources.clear();
1803         mPeriodicAdvCallbacksMap.clear();
1804         mBisDiscoveryCounterMap.clear();
1805 
1806         mSyncHandleToDeviceMap.clear();
1807         mSyncHandleToBaseDataMap.clear();
1808         mSyncHandleToBroadcastIdMap.clear();
1809         mPeriodicAdvertisementResultMap.clear();
1810     }
1811 
1812     /**
1813      * Return true if a search has been started by this application
1814      *
1815      * @return true if a search has been started by this application
1816      */
isSearchInProgress()1817     public boolean isSearchInProgress() {
1818         synchronized (mSearchScanCallbackLock) {
1819             return mSearchScanCallback != null;
1820         }
1821     }
1822 
1823     /** Internal periodc Advertising manager callback */
1824     final class PACallback extends PeriodicAdvertisingCallback {
1825         @Override
onSyncEstablished( int syncHandle, BluetoothDevice device, int advertisingSid, int skip, int timeout, int status)1826         public void onSyncEstablished(
1827                 int syncHandle,
1828                 BluetoothDevice device,
1829                 int advertisingSid,
1830                 int skip,
1831                 int timeout,
1832                 int status) {
1833             log(
1834                     "onSyncEstablished syncHandle: "
1835                             + syncHandle
1836                             + ", device: "
1837                             + device
1838                             + ", advertisingSid: "
1839                             + advertisingSid
1840                             + ", skip: "
1841                             + skip
1842                             + ", timeout: "
1843                             + timeout
1844                             + ", status: "
1845                             + status);
1846             if (status == BluetoothGatt.GATT_SUCCESS) {
1847                 // updates syncHandle, advSid
1848                 // set other fields as invalid or null
1849                 updatePeriodicAdvertisementResultMap(
1850                         device,
1851                         BassConstants.INVALID_ADV_ADDRESS_TYPE,
1852                         syncHandle,
1853                         advertisingSid,
1854                         BassConstants.INVALID_ADV_INTERVAL,
1855                         BassConstants.INVALID_BROADCAST_ID,
1856                         null,
1857                         null);
1858                 addActiveSyncedSource(syncHandle);
1859 
1860                 synchronized (mSearchScanCallbackLock) {
1861                     // when searching is stopped then start timer to stop active syncs
1862                     if (mSearchScanCallback == null) {
1863                         mHandler.removeMessages(MESSAGE_SYNC_TIMEOUT);
1864                         log("onSyncEstablished started timeout for canceling syncs");
1865                         mHandler.sendEmptyMessageDelayed(
1866                                 MESSAGE_SYNC_TIMEOUT, sSyncActiveTimeout.toMillis());
1867                     }
1868                 }
1869 
1870                 // update valid sync handle in mPeriodicAdvCallbacksMap
1871                 synchronized (mPeriodicAdvCallbacksMap) {
1872                     if (mPeriodicAdvCallbacksMap.containsKey(BassConstants.INVALID_SYNC_HANDLE)) {
1873                         PeriodicAdvertisingCallback paCb =
1874                                 mPeriodicAdvCallbacksMap.get(BassConstants.INVALID_SYNC_HANDLE);
1875                         mPeriodicAdvCallbacksMap.put(syncHandle, paCb);
1876                         mPeriodicAdvCallbacksMap.remove(BassConstants.INVALID_SYNC_HANDLE);
1877                     }
1878                 }
1879                 mBisDiscoveryCounterMap.put(syncHandle, MAX_BIS_DISCOVERY_TRIES_NUM);
1880 
1881                 synchronized (mPendingSourcesToAdd) {
1882                     Iterator<AddSourceData> iterator = mPendingSourcesToAdd.iterator();
1883                     while (iterator.hasNext()) {
1884                         AddSourceData pendingSourcesToAdd = iterator.next();
1885                         BluetoothDevice sourceDevice =
1886                                 pendingSourcesToAdd.mSourceMetadata.getSourceDevice();
1887                         if (sourceDevice.equals(device)) {
1888                             addSource(
1889                                     pendingSourcesToAdd.mSink,
1890                                     pendingSourcesToAdd.mSourceMetadata,
1891                                     pendingSourcesToAdd.mIsGroupOp);
1892                             iterator.remove();
1893                         }
1894                     }
1895                 }
1896             } else {
1897                 // remove failed sync handle
1898                 int broadcastId = getBroadcastIdForSyncHandle(BassConstants.INVALID_SYNC_HANDLE);
1899                 log("onSyncEstablished failed for broadcast id: " + broadcastId);
1900                 mCachedBroadcasts.remove(broadcastId);
1901                 mPeriodicAdvCallbacksMap.remove(BassConstants.INVALID_SYNC_HANDLE);
1902             }
1903             handleSelectSourceRequest();
1904         }
1905 
1906         @Override
onPeriodicAdvertisingReport(PeriodicAdvertisingReport report)1907         public void onPeriodicAdvertisingReport(PeriodicAdvertisingReport report) {
1908             int syncHandle = report.getSyncHandle();
1909             log("onPeriodicAdvertisingReport " + syncHandle);
1910             Integer bisCounter = mBisDiscoveryCounterMap.get(syncHandle);
1911 
1912             // Parse the BIS indices from report's service data
1913             if (bisCounter != null && bisCounter != 0) {
1914                 if (parseScanRecord(syncHandle, report.getData())) {
1915                     mBisDiscoveryCounterMap.put(syncHandle, 0);
1916                 } else {
1917                     bisCounter--;
1918                     mBisDiscoveryCounterMap.put(syncHandle, bisCounter);
1919                     if (bisCounter == 0) {
1920                         cancelActiveSync(syncHandle);
1921                     }
1922                 }
1923             }
1924         }
1925 
1926         @Override
onSyncLost(int syncHandle)1927         public void onSyncLost(int syncHandle) {
1928             int broadcastId = getBroadcastIdForSyncHandle(syncHandle);
1929             log("OnSyncLost: syncHandle=" + syncHandle + ", broadcastID=" + broadcastId);
1930             if (leaudioBroadcastMonitorSourceSyncStatus()) {
1931                 if (broadcastId != BassConstants.INVALID_BROADCAST_ID) {
1932                     log("Notify broadcast source lost, broadcast id: " + broadcastId);
1933                     mCallbacks.notifySourceLost(broadcastId);
1934                 }
1935             }
1936             clearAllDataForSyncHandle(syncHandle);
1937             // Clear from cache to make possible sync again
1938             mCachedBroadcasts.remove(broadcastId);
1939         }
1940 
1941         @Override
onBigInfoAdvertisingReport(int syncHandle, boolean encrypted)1942         public void onBigInfoAdvertisingReport(int syncHandle, boolean encrypted) {
1943             log(
1944                     "onBIGInfoAdvertisingReport: syncHandle="
1945                             + syncHandle
1946                             + ", encrypted ="
1947                             + encrypted);
1948             BluetoothDevice srcDevice = getDeviceForSyncHandle(syncHandle);
1949             if (srcDevice == null) {
1950                 log("No device found.");
1951                 return;
1952             }
1953             PeriodicAdvertisementResult result =
1954                     getPeriodicAdvertisementResult(
1955                             srcDevice, getBroadcastIdForSyncHandle(syncHandle));
1956             if (result == null) {
1957                 log("No PA record found");
1958                 return;
1959             }
1960             if (!result.isNotified()) {
1961                 result.setNotified(true);
1962                 BaseData baseData = getBase(syncHandle);
1963                 if (baseData == null) {
1964                     log("No BaseData found");
1965                     return;
1966                 }
1967                 BluetoothLeBroadcastMetadata metaData =
1968                         getBroadcastMetadataFromBaseData(
1969                                 baseData, srcDevice, syncHandle, encrypted);
1970                 log("Notify broadcast source found");
1971                 mCallbacks.notifySourceFound(metaData);
1972             }
1973         }
1974 
1975         @Override
onSyncTransferred(BluetoothDevice device, int status)1976         public void onSyncTransferred(BluetoothDevice device, int status) {
1977             log("onSyncTransferred: device=" + device + ", status =" + status);
1978         }
1979     }
1980 
clearAllDataForSyncHandle(Integer syncHandle)1981     private void clearAllDataForSyncHandle(Integer syncHandle) {
1982         removeActiveSyncedSource(syncHandle);
1983         mPeriodicAdvCallbacksMap.remove(syncHandle);
1984         mSyncHandleToBaseDataMap.remove(syncHandle);
1985         mBisDiscoveryCounterMap.remove(syncHandle);
1986         BluetoothDevice srcDevice = getDeviceForSyncHandle(syncHandle);
1987         mSyncHandleToDeviceMap.remove(syncHandle);
1988         int broadcastId = getBroadcastIdForSyncHandle(syncHandle);
1989         mSyncHandleToBroadcastIdMap.remove(syncHandle);
1990         if (srcDevice != null) {
1991             mPeriodicAdvertisementResultMap.get(srcDevice).remove(broadcastId);
1992             if (mPeriodicAdvertisementResultMap.get(srcDevice).isEmpty()) {
1993                 mPeriodicAdvertisementResultMap.remove(srcDevice);
1994             }
1995         }
1996     }
1997 
getBroadcastMetadataFromBaseData( BaseData baseData, BluetoothDevice device, int syncHandle, boolean encrypted)1998     private BluetoothLeBroadcastMetadata getBroadcastMetadataFromBaseData(
1999             BaseData baseData, BluetoothDevice device, int syncHandle, boolean encrypted) {
2000         BluetoothLeBroadcastMetadata.Builder metaData = new BluetoothLeBroadcastMetadata.Builder();
2001         int index = 0;
2002         for (BaseData.BaseInformation baseLevel2 : baseData.getLevelTwo()) {
2003             BluetoothLeBroadcastSubgroup.Builder subGroup =
2004                     new BluetoothLeBroadcastSubgroup.Builder();
2005             for (int j = 0; j < baseLevel2.numSubGroups; j++) {
2006                 BaseData.BaseInformation baseLevel3 = baseData.getLevelThree().get(index++);
2007                 BluetoothLeBroadcastChannel.Builder channel =
2008                         new BluetoothLeBroadcastChannel.Builder();
2009                 channel.setChannelIndex(baseLevel3.index);
2010                 channel.setSelected(false);
2011                 try {
2012                     channel.setCodecMetadata(
2013                             BluetoothLeAudioCodecConfigMetadata.fromRawBytes(
2014                                     baseLevel3.codecConfigInfo));
2015                 } catch (IllegalArgumentException e) {
2016                     Log.w(TAG, "Invalid metadata, adding empty data. Error: " + e);
2017                     channel.setCodecMetadata(
2018                             BluetoothLeAudioCodecConfigMetadata.fromRawBytes(new byte[0]));
2019                 }
2020                 subGroup.addChannel(channel.build());
2021             }
2022             byte[] arrayCodecId = baseLevel2.codecId;
2023             long codeId =
2024                     ((long) (arrayCodecId[4] & 0xff)) << 32
2025                             | (arrayCodecId[3] & 0xff) << 24
2026                             | (arrayCodecId[2] & 0xff) << 16
2027                             | (arrayCodecId[1] & 0xff) << 8
2028                             | (arrayCodecId[0] & 0xff);
2029             subGroup.setCodecId(codeId);
2030             try {
2031                 subGroup.setCodecSpecificConfig(
2032                         BluetoothLeAudioCodecConfigMetadata.fromRawBytes(
2033                                 baseLevel2.codecConfigInfo));
2034             } catch (IllegalArgumentException e) {
2035                 Log.w(TAG, "Invalid config, adding empty one. Error: " + e);
2036                 subGroup.setCodecSpecificConfig(
2037                         BluetoothLeAudioCodecConfigMetadata.fromRawBytes(new byte[0]));
2038             }
2039 
2040             try {
2041                 subGroup.setContentMetadata(
2042                         BluetoothLeAudioContentMetadata.fromRawBytes(baseLevel2.metaData));
2043             } catch (IllegalArgumentException e) {
2044                 Log.w(TAG, "Invalid metadata, adding empty one. Error: " + e);
2045                 subGroup.setContentMetadata(
2046                         BluetoothLeAudioContentMetadata.fromRawBytes(new byte[0]));
2047             }
2048 
2049             metaData.addSubgroup(subGroup.build());
2050         }
2051         metaData.setSourceDevice(device, device.getAddressType());
2052         byte[] arrayPresentationDelay = baseData.getLevelOne().presentationDelay;
2053         int presentationDelay =
2054                 (int)
2055                         ((arrayPresentationDelay[2] & 0xff) << 16
2056                                 | (arrayPresentationDelay[1] & 0xff) << 8
2057                                 | (arrayPresentationDelay[0] & 0xff));
2058         metaData.setPresentationDelayMicros(presentationDelay);
2059         PeriodicAdvertisementResult result =
2060                 getPeriodicAdvertisementResult(device, getBroadcastIdForSyncHandle(syncHandle));
2061         if (result != null) {
2062             int broadcastId = result.getBroadcastId();
2063             log("broadcast ID: " + broadcastId);
2064             metaData.setBroadcastId(broadcastId);
2065             metaData.setSourceAdvertisingSid(result.getAdvSid());
2066 
2067             PublicBroadcastData pbData = result.getPublicBroadcastData();
2068             if (pbData != null) {
2069                 metaData.setPublicBroadcast(true);
2070                 metaData.setAudioConfigQuality(pbData.getAudioConfigQuality());
2071                 try {
2072                     metaData.setPublicBroadcastMetadata(
2073                             BluetoothLeAudioContentMetadata.fromRawBytes(pbData.getMetadata()));
2074                 } catch (IllegalArgumentException e) {
2075                     Log.w(TAG, "Invalid public metadata, adding empty one. Error " + e);
2076                     metaData.setPublicBroadcastMetadata(null);
2077                 }
2078             }
2079 
2080             String broadcastName = result.getBroadcastName();
2081             if (broadcastName != null) {
2082                 metaData.setBroadcastName(broadcastName);
2083             }
2084         }
2085         metaData.setEncrypted(encrypted);
2086         if (leaudioBroadcastMonitorSourceSyncStatus()) {
2087             // update the rssi value
2088             ScanResult scanRes = getCachedBroadcast(result.getBroadcastId());
2089             if (scanRes != null) {
2090                 metaData.setRssi(scanRes.getRssi());
2091             }
2092         }
2093         return metaData.build();
2094     }
2095 
cancelActiveSync(Integer syncHandle)2096     private void cancelActiveSync(Integer syncHandle) {
2097         log("cancelActiveSync: syncHandle = " + syncHandle);
2098         if (syncHandle == null) {
2099             // clean up the pending sync request if syncHandle is null
2100             unsyncSource(BassConstants.INVALID_SYNC_HANDLE);
2101         }
2102         List<Integer> activeSyncedSrc = new ArrayList<>(getActiveSyncedSources());
2103 
2104         /* Stop sync if there is some running */
2105         if (!activeSyncedSrc.isEmpty()
2106                 && (syncHandle == null || activeSyncedSrc.contains(syncHandle))) {
2107             if (syncHandle != null) {
2108                 // only one source needs to be unsynced
2109                 unsyncSource(syncHandle);
2110             } else {
2111                 // remove all the sources
2112                 for (int handle : activeSyncedSrc) {
2113                     unsyncSource(handle);
2114                 }
2115             }
2116         }
2117     }
2118 
unsyncSource(int syncHandle)2119     private boolean unsyncSource(int syncHandle) {
2120         log("unsyncSource: syncHandle: " + syncHandle);
2121         if (mPeriodicAdvCallbacksMap.containsKey(syncHandle)) {
2122             try {
2123                 BluetoothMethodProxy.getInstance()
2124                         .periodicAdvertisingManagerUnregisterSync(
2125                                 BassClientPeriodicAdvertisingManager
2126                                         .getPeriodicAdvertisingManager(),
2127                                 mPeriodicAdvCallbacksMap.get(syncHandle));
2128             } catch (IllegalArgumentException ex) {
2129                 Log.w(TAG, "unregisterSync:IllegalArgumentException");
2130                 return false;
2131             }
2132         } else {
2133             log("calling unregisterSync, not found syncHandle: " + syncHandle);
2134         }
2135         clearAllDataForSyncHandle(syncHandle);
2136         return true;
2137     }
2138 
parseBaseData(int syncHandle, byte[] serviceData)2139     boolean parseBaseData(int syncHandle, byte[] serviceData) {
2140         log("parseBaseData" + Arrays.toString(serviceData));
2141         BaseData base = BaseData.parseBaseData(serviceData);
2142         if (base != null) {
2143             updateBase(syncHandle, base);
2144             base.print();
2145             return true;
2146         } else {
2147             Log.e(TAG, "Seems BASE is not in parsable format");
2148         }
2149         return false;
2150     }
2151 
parseScanRecord(int syncHandle, ScanRecord record)2152     boolean parseScanRecord(int syncHandle, ScanRecord record) {
2153         int broadcastId = getBroadcastIdForSyncHandle(syncHandle);
2154         log(
2155                 "parseScanRecord: syncHandle="
2156                         + syncHandle
2157                         + ", broadcastID="
2158                         + broadcastId
2159                         + ", record="
2160                         + record);
2161         Map<ParcelUuid, byte[]> bmsAdvDataMap = record.getServiceData();
2162         if (bmsAdvDataMap != null) {
2163             for (Map.Entry<ParcelUuid, byte[]> entry : bmsAdvDataMap.entrySet()) {
2164                 log(
2165                         "ParcelUUid = "
2166                                 + entry.getKey()
2167                                 + ", Value = "
2168                                 + Arrays.toString(entry.getValue()));
2169             }
2170         }
2171         byte[] advData = record.getServiceData(BassConstants.BASIC_AUDIO_UUID);
2172         if (advData != null) {
2173             return parseBaseData(syncHandle, advData);
2174         } else {
2175             Log.e(TAG, "No service data in Scan record");
2176         }
2177         return false;
2178     }
2179 
checkAndParseBroadcastName(ScanRecord record)2180     private String checkAndParseBroadcastName(ScanRecord record) {
2181         log("checkAndParseBroadcastName");
2182         byte[] rawBytes = record.getBytes();
2183         List<TypeValueEntry> entries = BluetoothUtils.parseLengthTypeValueBytes(rawBytes);
2184         if (rawBytes.length > 0 && rawBytes[0] > 0 && entries.isEmpty()) {
2185             Log.e(TAG, "Invalid LTV entries in Scan record");
2186             return null;
2187         }
2188 
2189         String broadcastName = null;
2190         for (TypeValueEntry entry : entries) {
2191             // Only use the first value of each type
2192             if (broadcastName == null && entry.getType() == BassConstants.BCAST_NAME_AD_TYPE) {
2193                 byte[] bytes = entry.getValue();
2194                 int len = bytes.length;
2195                 if (len < BassConstants.BCAST_NAME_LEN_MIN
2196                         || len > BassConstants.BCAST_NAME_LEN_MAX) {
2197                     Log.e(TAG, "Invalid broadcast name length in Scan record" + len);
2198                     return null;
2199                 }
2200                 broadcastName = new String(bytes, StandardCharsets.UTF_8);
2201             }
2202         }
2203         return broadcastName;
2204     }
2205 
addSelectSourceRequest(ScanResult scanRes, boolean hasPriority)2206     void addSelectSourceRequest(ScanResult scanRes, boolean hasPriority) {
2207         sEventLogger.logd(
2208                 TAG,
2209                 "Add Select Broadcast Source, result: "
2210                         + scanRes
2211                         + ", hasPriority: "
2212                         + hasPriority);
2213 
2214         ScanRecord scanRecord = scanRes.getScanRecord();
2215         if (scanRecord == null) {
2216             log("addSelectSourceRequest: ScanRecord empty");
2217             return;
2218         }
2219 
2220         synchronized (mSourceSyncRequestsQueue) {
2221             mSourceSyncRequestsQueue.add(new SourceSyncRequest(scanRes, hasPriority));
2222         }
2223 
2224         handleSelectSourceRequest();
2225     }
2226 
handleSelectSourceRequest()2227     private void handleSelectSourceRequest() {
2228         PeriodicAdvertisingCallback paCb;
2229         synchronized (mPeriodicAdvCallbacksMap) {
2230             if (mSourceSyncRequestsQueue.isEmpty()) {
2231                 return;
2232             } else if (mPeriodicAdvCallbacksMap.containsKey(BassConstants.INVALID_SYNC_HANDLE)) {
2233                 log("handleSelectSourceRequest: already pending sync");
2234                 return;
2235             } else {
2236                 paCb = new PACallback();
2237                 // put INVALID_SYNC_HANDLE and update in onSyncEstablished
2238                 mPeriodicAdvCallbacksMap.put(BassConstants.INVALID_SYNC_HANDLE, paCb);
2239             }
2240         }
2241         ScanResult scanRes;
2242         synchronized (mSourceSyncRequestsQueue) {
2243             scanRes = mSourceSyncRequestsQueue.poll().getScanResult();
2244         }
2245         ScanRecord scanRecord = scanRes.getScanRecord();
2246 
2247         sEventLogger.logd(TAG, "Select Broadcast Source, result: " + scanRes);
2248 
2249         // updating mainly for Address type and PA Interval here
2250         // extract BroadcastId from ScanResult
2251         Map<ParcelUuid, byte[]> listOfUuids = scanRecord.getServiceData();
2252         int broadcastId = BassConstants.INVALID_BROADCAST_ID;
2253         PublicBroadcastData pbData = null;
2254         if (listOfUuids != null) {
2255             if (listOfUuids.containsKey(BassConstants.BAAS_UUID)) {
2256                 byte[] bId = listOfUuids.get(BassConstants.BAAS_UUID);
2257                 broadcastId = BassUtils.parseBroadcastId(bId);
2258             }
2259             if (listOfUuids.containsKey(BassConstants.PUBLIC_BROADCAST_UUID)) {
2260                 byte[] pbAnnouncement = listOfUuids.get(BassConstants.PUBLIC_BROADCAST_UUID);
2261                 pbData = PublicBroadcastData.parsePublicBroadcastData(pbAnnouncement);
2262             }
2263         }
2264 
2265         if (broadcastId == BassConstants.INVALID_BROADCAST_ID || pbData == null) {
2266             Log.w(TAG, "Invalid broadcast ID or public broadcast data");
2267             mPeriodicAdvCallbacksMap.remove(BassConstants.INVALID_SYNC_HANDLE);
2268             handleSelectSourceRequest();
2269             return;
2270         }
2271 
2272         // Check if broadcast name present in scan record and parse
2273         // null if no name present
2274         String broadcastName = checkAndParseBroadcastName(scanRecord);
2275 
2276         // Avoid duplicated sync request if the same broadcast BIG is synced
2277         List<Integer> activeSyncedSrc = getActiveSyncedSources();
2278         if (activeSyncedSrc.contains(getSyncHandleForBroadcastId(broadcastId))) {
2279             log("Skip duplicated sync request to broadcast id: " + broadcastId);
2280             mPeriodicAdvCallbacksMap.remove(BassConstants.INVALID_SYNC_HANDLE);
2281             handleSelectSourceRequest();
2282             return;
2283         }
2284 
2285         // Check if there are resources for sync
2286         if (activeSyncedSrc.size() >= MAX_ACTIVE_SYNCED_SOURCES_NUM) {
2287             log("handleSelectSourceRequest: reached max allowed active source");
2288             int syncHandle = activeSyncedSrc.get(0);
2289             // removing the 1st synced source before proceeding to add new
2290             cancelActiveSync(syncHandle);
2291         }
2292 
2293         try {
2294             BluetoothMethodProxy.getInstance()
2295                     .periodicAdvertisingManagerRegisterSync(
2296                             BassClientPeriodicAdvertisingManager.getPeriodicAdvertisingManager(),
2297                             scanRes,
2298                             0,
2299                             BassConstants.PSYNC_TIMEOUT,
2300                             paCb,
2301                             null);
2302         } catch (IllegalArgumentException ex) {
2303             Log.w(TAG, "registerSync:IllegalArgumentException");
2304             mPeriodicAdvCallbacksMap.remove(BassConstants.INVALID_SYNC_HANDLE);
2305             handleSelectSourceRequest();
2306             return;
2307         }
2308 
2309         updateSyncHandleForBroadcastId(BassConstants.INVALID_SYNC_HANDLE, broadcastId);
2310         updatePeriodicAdvertisementResultMap(
2311                 scanRes.getDevice(),
2312                 scanRes.getDevice().getAddressType(),
2313                 BassConstants.INVALID_SYNC_HANDLE,
2314                 BassConstants.INVALID_ADV_SID,
2315                 scanRes.getPeriodicAdvertisingInterval(),
2316                 broadcastId,
2317                 pbData,
2318                 broadcastName);
2319     }
2320 
selectSource(BluetoothDevice sink, ScanResult result, boolean autoTrigger)2321     void selectSource(BluetoothDevice sink, ScanResult result, boolean autoTrigger) {
2322         if (leaudioBroadcastExtractPeriodicScannerFromStateMachine()) {
2323             throw new RuntimeException(
2324                     "Should never be executed with"
2325                             + " leaudioBroadcastExtractPeriodicScannerFromStateMachine flag");
2326         }
2327         List<Integer> activeSyncedSrc = getActiveSyncedSources(sink);
2328         if (activeSyncedSrc != null && activeSyncedSrc.size() >= MAX_ACTIVE_SYNCED_SOURCES_NUM) {
2329             log("selectSource : reached max allowed active source");
2330             int syncHandle = activeSyncedSrc.get(0);
2331             // removing the 1st synced source before proceeding to add new
2332             synchronized (mStateMachines) {
2333                 BassClientStateMachine stateMachine = getOrCreateStateMachine(sink);
2334                 if (stateMachine == null) {
2335                     Log.e(TAG, "Can't get state machine for device: " + sink);
2336                     return;
2337                 }
2338                 Message message =
2339                         stateMachine.obtainMessage(BassClientStateMachine.REACHED_MAX_SOURCE_LIMIT);
2340                 message.arg1 = syncHandle;
2341                 stateMachine.sendMessage(message);
2342             }
2343         }
2344 
2345         synchronized (mStateMachines) {
2346             sEventLogger.logd(
2347                     TAG, "Select Broadcast Source: sink: " + sink + ", result: " + result);
2348 
2349             BassClientStateMachine stateMachine = getOrCreateStateMachine(sink);
2350             if (stateMachine == null) {
2351                 Log.e(TAG, "Can't get state machine for device: " + sink);
2352                 return;
2353             }
2354             Message message =
2355                     stateMachine.obtainMessage(BassClientStateMachine.SELECT_BCAST_SOURCE);
2356             message.obj = result;
2357             message.arg1 = autoTrigger ? BassConstants.AUTO : BassConstants.USER;
2358             stateMachine.sendMessage(message);
2359         }
2360     }
2361 
2362     /**
2363      * Add a Broadcast Source to the Broadcast Sink
2364      *
2365      * @param sink Broadcast Sink to which the Broadcast Source should be added
2366      * @param sourceMetadata Broadcast Source metadata to be added to the Broadcast Sink
2367      * @param isGroupOp set to true If Application wants to perform this operation for all
2368      *     coordinated set members, False otherwise
2369      */
addSource( BluetoothDevice sink, BluetoothLeBroadcastMetadata sourceMetadata, boolean isGroupOp)2370     public void addSource(
2371             BluetoothDevice sink, BluetoothLeBroadcastMetadata sourceMetadata, boolean isGroupOp) {
2372         log(
2373                 "addSource: "
2374                         + ("device: " + sink)
2375                         + (", sourceMetadata: " + sourceMetadata)
2376                         + (", isGroupOp: " + isGroupOp));
2377 
2378         List<BluetoothDevice> devices = getTargetDeviceList(sink, isGroupOp);
2379         // Don't coordinate it as a group if there's no group or there is one device only
2380         if (devices.size() < 2) {
2381             isGroupOp = false;
2382         }
2383 
2384         if (sourceMetadata == null) {
2385             log("addSource: Error bad parameter: sourceMetadata cannot be null");
2386             return;
2387         }
2388 
2389         if (leaudioBroadcastAssistantPeripheralEntrustment()) {
2390             if (isLocalBroadcast(sourceMetadata)) {
2391                 LeAudioService leAudioService = mServiceFactory.getLeAudioService();
2392                 if (leAudioService == null
2393                         || !leAudioService.isPlaying(sourceMetadata.getBroadcastId())) {
2394                     Log.w(TAG, "addSource: Local source can't be add");
2395 
2396                     mCallbacks.notifySourceAddFailed(
2397                             sink,
2398                             sourceMetadata,
2399                             BluetoothStatusCodes.ERROR_LOCAL_NOT_ENOUGH_RESOURCES);
2400 
2401                     return;
2402                 }
2403             }
2404         } else {
2405             if (!isAllowedToAddSource()) {
2406                 Log.d(TAG, "Add source to pending list");
2407                 mPendingAddSources.push(new AddSourceData(sink, sourceMetadata, isGroupOp));
2408 
2409                 return;
2410             }
2411         }
2412 
2413         if (leaudioBroadcastExtractPeriodicScannerFromStateMachine()) {
2414             List<Integer> activeSyncedSrc = getActiveSyncedSources();
2415             BluetoothDevice sourceDevice = sourceMetadata.getSourceDevice();
2416             if (!isLocalBroadcast(sourceMetadata)
2417                     && (!activeSyncedSrc.contains(
2418                             getSyncHandleForBroadcastId(sourceMetadata.getBroadcastId())))) {
2419                 log("Adding inactive source: " + sourceDevice);
2420                 int broadcastId = sourceMetadata.getBroadcastId();
2421                 if (broadcastId != BassConstants.INVALID_BROADCAST_ID
2422                         && getCachedBroadcast(broadcastId) != null) {
2423                     // If the source has been synced before, try to re-sync
2424                     // with the source by previously cached scan result
2425                     addSelectSourceRequest(getCachedBroadcast(broadcastId), true);
2426                     synchronized (mPendingSourcesToAdd) {
2427                         mPendingSourcesToAdd.add(
2428                                 new AddSourceData(sink, sourceMetadata, isGroupOp));
2429                     }
2430                 } else {
2431                     log("AddSource: broadcast not cached or invalid, broadcastId: " + broadcastId);
2432                     mCallbacks.notifySourceAddFailed(
2433                             sink, sourceMetadata, BluetoothStatusCodes.ERROR_UNKNOWN);
2434                 }
2435                 return;
2436             }
2437         }
2438 
2439         byte[] code = sourceMetadata.getBroadcastCode();
2440         for (BluetoothDevice device : devices) {
2441             BassClientStateMachine stateMachine = getOrCreateStateMachine(device);
2442             if (stateMachine == null) {
2443                 log("addSource: Error bad parameter: no state machine for " + device);
2444                 mCallbacks.notifySourceAddFailed(
2445                         device, sourceMetadata, BluetoothStatusCodes.ERROR_BAD_PARAMETERS);
2446                 continue;
2447             }
2448             if (getConnectionState(device) != BluetoothProfile.STATE_CONNECTED) {
2449                 log("addSource: device is not connected");
2450                 mCallbacks.notifySourceAddFailed(
2451                         device, sourceMetadata, BluetoothStatusCodes.ERROR_REMOTE_LINK_ERROR);
2452                 continue;
2453             }
2454             if (stateMachine.hasPendingSourceOperation()) {
2455                 Log.w(
2456                         TAG,
2457                         "addSource: source operation already pending, device: "
2458                                 + device
2459                                 + ", broadcastId: "
2460                                 + sourceMetadata.getBroadcastId());
2461                 mCallbacks.notifySourceAddFailed(
2462                         device, sourceMetadata, BluetoothStatusCodes.ERROR_ALREADY_IN_TARGET_STATE);
2463                 continue;
2464             }
2465             if (!hasRoomForBroadcastSourceAddition(device)) {
2466                 log("addSource: device has no room");
2467                 Integer sourceId = getSourceIdToRemove(device);
2468                 if (sourceId != BassConstants.INVALID_SOURCE_ID) {
2469                     sEventLogger.logd(
2470                             TAG,
2471                             "Switch Broadcast Source: "
2472                                     + ("device: " + device)
2473                                     + (", old SourceId: " + sourceId)
2474                                     + (", new broadcastId: " + sourceMetadata.getBroadcastId())
2475                                     + (", new broadcastName: "
2476                                             + sourceMetadata.getBroadcastName()));
2477 
2478                     // new source will be added once the existing source got removed
2479                     if (isGroupOp) {
2480                         // mark group op for both remove and add source
2481                         // so setSourceGroupManaged will be updated accordingly in callbacks
2482                         enqueueSourceGroupOp(
2483                                 device, BassClientStateMachine.REMOVE_BCAST_SOURCE, sourceId);
2484                         enqueueSourceGroupOp(
2485                                 device, BassClientStateMachine.ADD_BCAST_SOURCE, sourceMetadata);
2486                     }
2487 
2488                     /* Store metadata for sink device */
2489                     mBroadcastMetadataMap.put(device, sourceMetadata);
2490 
2491                     Message message =
2492                             stateMachine.obtainMessage(BassClientStateMachine.SWITCH_BCAST_SOURCE);
2493                     message.obj = sourceMetadata;
2494                     message.arg1 = sourceId;
2495                     stateMachine.sendMessage(message);
2496                 } else {
2497                     mCallbacks.notifySourceAddFailed(
2498                             device,
2499                             sourceMetadata,
2500                             BluetoothStatusCodes.ERROR_REMOTE_NOT_ENOUGH_RESOURCES);
2501                 }
2502                 continue;
2503             }
2504             if (!isValidBroadcastSourceAddition(device, sourceMetadata)) {
2505                 log("addSource: not a valid broadcast source addition");
2506                 mCallbacks.notifySourceAddFailed(
2507                         device,
2508                         sourceMetadata,
2509                         BluetoothStatusCodes.ERROR_LE_BROADCAST_ASSISTANT_DUPLICATE_ADDITION);
2510                 continue;
2511             }
2512             if ((code != null) && (code.length != 0)) {
2513                 if ((code.length > 16) || (code.length < 4)) {
2514                     log(
2515                             "Invalid broadcast code length: "
2516                                     + code.length
2517                                     + ", should be between 4 and 16 octets");
2518                     mCallbacks.notifySourceAddFailed(
2519                             device, sourceMetadata, BluetoothStatusCodes.ERROR_BAD_PARAMETERS);
2520                     continue;
2521                 }
2522             }
2523 
2524             /* Store metadata for sink device */
2525             mBroadcastMetadataMap.put(device, sourceMetadata);
2526 
2527             if (isGroupOp) {
2528                 enqueueSourceGroupOp(
2529                         device, BassClientStateMachine.ADD_BCAST_SOURCE, sourceMetadata);
2530             }
2531 
2532             sEventLogger.logd(
2533                     TAG,
2534                     "Add Broadcast Source: "
2535                             + ("device: " + device)
2536                             + (", broadcastId: " + sourceMetadata.getBroadcastId())
2537                             + (", broadcastName: " + sourceMetadata.getBroadcastName())
2538                             + (", isGroupOp: " + isGroupOp));
2539 
2540             Message message = stateMachine.obtainMessage(BassClientStateMachine.ADD_BCAST_SOURCE);
2541             message.obj = sourceMetadata;
2542             stateMachine.sendMessage(message);
2543             if (code != null && code.length != 0) {
2544                 sEventLogger.logd(
2545                         TAG,
2546                         "Set Broadcast Code (Add Source context): "
2547                                 + ("device: " + device)
2548                                 + (", broadcastId: " + sourceMetadata.getBroadcastId())
2549                                 + (", broadcastName: " + sourceMetadata.getBroadcastName()));
2550 
2551                 message = stateMachine.obtainMessage(BassClientStateMachine.SET_BCAST_CODE);
2552                 message.obj = sourceMetadata;
2553                 message.arg1 = BassClientStateMachine.ARGTYPE_METADATA;
2554                 stateMachine.sendMessage(message);
2555             }
2556         }
2557     }
2558 
2559     /**
2560      * Modify the Broadcast Source information on a Broadcast Sink
2561      *
2562      * @param sink representing the Broadcast Sink to which the Broadcast Source should be updated
2563      * @param sourceId source ID as delivered in onSourceAdded
2564      * @param updatedMetadata updated Broadcast Source metadata to be updated on the Broadcast Sink
2565      */
modifySource( BluetoothDevice sink, int sourceId, BluetoothLeBroadcastMetadata updatedMetadata)2566     public void modifySource(
2567             BluetoothDevice sink, int sourceId, BluetoothLeBroadcastMetadata updatedMetadata) {
2568         log(
2569                 "modifySource: "
2570                         + ("device: " + sink)
2571                         + ("sourceId: " + sourceId)
2572                         + (", updatedMetadata: " + updatedMetadata));
2573 
2574         Map<BluetoothDevice, Integer> devices = getGroupManagedDeviceSources(sink, sourceId).second;
2575         if (updatedMetadata == null) {
2576             log("modifySource: Error bad parameters: updatedMetadata cannot be null");
2577             for (BluetoothDevice device : devices.keySet()) {
2578                 mCallbacks.notifySourceModifyFailed(
2579                         device, sourceId, BluetoothStatusCodes.ERROR_BAD_PARAMETERS);
2580             }
2581             return;
2582         }
2583 
2584         /* Update metadata for sink device */
2585         mBroadcastMetadataMap.put(sink, updatedMetadata);
2586 
2587         byte[] code = updatedMetadata.getBroadcastCode();
2588         for (Map.Entry<BluetoothDevice, Integer> deviceSourceIdPair : devices.entrySet()) {
2589             BluetoothDevice device = deviceSourceIdPair.getKey();
2590             Integer deviceSourceId = deviceSourceIdPair.getValue();
2591             BassClientStateMachine stateMachine = getOrCreateStateMachine(device);
2592 
2593             int statusCode =
2594                     areValidParametersToModifySource(
2595                             updatedMetadata, stateMachine, deviceSourceId, device);
2596             if (statusCode != BluetoothStatusCodes.SUCCESS) {
2597                 mCallbacks.notifySourceModifyFailed(device, sourceId, statusCode);
2598                 continue;
2599             }
2600 
2601             sEventLogger.logd(
2602                     TAG,
2603                     "Modify Broadcast Source: "
2604                             + ("device: " + device)
2605                             + ("sourceId: " + sourceId)
2606                             + (", updatedBroadcastId: " + updatedMetadata.getBroadcastId())
2607                             + (", updatedBroadcastName: " + updatedMetadata.getBroadcastName()));
2608 
2609             Message message =
2610                     stateMachine.obtainMessage(BassClientStateMachine.UPDATE_BCAST_SOURCE);
2611             message.arg1 = deviceSourceId;
2612             message.arg2 = BassConstants.INVALID_PA_SYNC_VALUE;
2613             message.obj = updatedMetadata;
2614             stateMachine.sendMessage(message);
2615             if (code != null && code.length != 0) {
2616                 sEventLogger.logd(
2617                         TAG,
2618                         "Set Broadcast Code (Modify Source context): "
2619                                 + ("device: " + device)
2620                                 + ("sourceId: " + sourceId)
2621                                 + (", updatedBroadcastId: " + updatedMetadata.getBroadcastId())
2622                                 + (", updatedBroadcastName: "
2623                                         + updatedMetadata.getBroadcastName()));
2624                 message = stateMachine.obtainMessage(BassClientStateMachine.SET_BCAST_CODE);
2625                 message.obj = updatedMetadata;
2626                 message.arg1 = BassClientStateMachine.ARGTYPE_METADATA;
2627                 stateMachine.sendMessage(message);
2628             }
2629         }
2630     }
2631 
2632     /**
2633      * Removes the Broadcast Source from a Broadcast Sink
2634      *
2635      * @param sink representing the Broadcast Sink from which a Broadcast Source should be removed
2636      * @param sourceId source ID as delivered in onSourceAdded
2637      */
removeSource(BluetoothDevice sink, int sourceId)2638     public void removeSource(BluetoothDevice sink, int sourceId) {
2639         log("removeSource: device: " + sink + ", sourceId: " + sourceId);
2640 
2641         Map<BluetoothDevice, Integer> devices = getGroupManagedDeviceSources(sink, sourceId).second;
2642         for (Map.Entry<BluetoothDevice, Integer> deviceSourceIdPair : devices.entrySet()) {
2643             BluetoothDevice device = deviceSourceIdPair.getKey();
2644             Integer deviceSourceId = deviceSourceIdPair.getValue();
2645             BassClientStateMachine stateMachine = getOrCreateStateMachine(device);
2646 
2647             /* Removes metadata for sink device if not paused */
2648             if (!mPausedBroadcastSinks.contains(device)) {
2649                 mBroadcastMetadataMap.remove(device);
2650             }
2651 
2652             if (stateMachine == null) {
2653                 log("removeSource: Error bad parameters: device = " + device);
2654                 mCallbacks.notifySourceRemoveFailed(
2655                         device, sourceId, BluetoothStatusCodes.ERROR_BAD_PARAMETERS);
2656                 continue;
2657             }
2658             if (deviceSourceId == BassConstants.INVALID_SOURCE_ID) {
2659                 log("removeSource: no such sourceId for device: " + device);
2660                 mCallbacks.notifySourceRemoveFailed(
2661                         device,
2662                         sourceId,
2663                         BluetoothStatusCodes.ERROR_LE_BROADCAST_ASSISTANT_INVALID_SOURCE_ID);
2664                 continue;
2665             }
2666             if (getConnectionState(device) != BluetoothProfile.STATE_CONNECTED) {
2667                 log("removeSource: device is not connected");
2668                 mCallbacks.notifySourceRemoveFailed(
2669                         device, sourceId, BluetoothStatusCodes.ERROR_REMOTE_LINK_ERROR);
2670                 continue;
2671             }
2672 
2673             BluetoothLeBroadcastMetadata metaData =
2674                     stateMachine.getCurrentBroadcastMetadata(sourceId);
2675             if (metaData != null && stateMachine.isSyncedToTheSource(sourceId)) {
2676                 sEventLogger.logd(
2677                         TAG,
2678                         "Remove Broadcast Source(Force lost PA sync): "
2679                                 + ("device: " + device)
2680                                 + (", sourceId: " + sourceId)
2681                                 + (", broadcastId: " + metaData.getBroadcastId())
2682                                 + (", broadcastName: " + metaData.getBroadcastName()));
2683 
2684                 log("Force source to lost PA sync");
2685                 Message message =
2686                         stateMachine.obtainMessage(BassClientStateMachine.UPDATE_BCAST_SOURCE);
2687                 message.arg1 = sourceId;
2688                 message.arg2 = BassConstants.PA_SYNC_DO_NOT_SYNC;
2689                 /* Pending remove set. Remove source once not synchronized to PA */
2690                 message.obj = metaData;
2691                 stateMachine.sendMessage(message);
2692 
2693                 continue;
2694             }
2695 
2696             sEventLogger.logd(
2697                     TAG, "Remove Broadcast Source: device: " + device + ", sourceId: " + sourceId);
2698 
2699             Message message =
2700                     stateMachine.obtainMessage(BassClientStateMachine.REMOVE_BCAST_SOURCE);
2701             message.arg1 = deviceSourceId;
2702             stateMachine.sendMessage(message);
2703         }
2704 
2705         for (Map.Entry<BluetoothDevice, Integer> deviceSourceIdPair : devices.entrySet()) {
2706             BluetoothDevice device = deviceSourceIdPair.getKey();
2707             Integer deviceSourceId = deviceSourceIdPair.getValue();
2708             enqueueSourceGroupOp(
2709                     device,
2710                     BassClientStateMachine.REMOVE_BCAST_SOURCE,
2711                     Integer.valueOf(deviceSourceId));
2712         }
2713     }
2714 
2715     /**
2716      * Get information about all Broadcast Sources
2717      *
2718      * @param sink Broadcast Sink from which to get all Broadcast Sources
2719      * @return the list of Broadcast Receive State {@link BluetoothLeBroadcastReceiveState}
2720      */
getAllSources(BluetoothDevice sink)2721     public List<BluetoothLeBroadcastReceiveState> getAllSources(BluetoothDevice sink) {
2722         log("getAllSources for " + sink);
2723         synchronized (mStateMachines) {
2724             BassClientStateMachine stateMachine = getOrCreateStateMachine(sink);
2725             if (stateMachine == null) {
2726                 log("stateMachine is null");
2727                 return Collections.emptyList();
2728             }
2729             List<BluetoothLeBroadcastReceiveState> recvStates =
2730                     new ArrayList<BluetoothLeBroadcastReceiveState>();
2731             for (BluetoothLeBroadcastReceiveState rs : stateMachine.getAllSources()) {
2732                 String emptyBluetoothDevice = "00:00:00:00:00:00";
2733                 if (!rs.getSourceDevice().getAddress().equals(emptyBluetoothDevice)) {
2734                     recvStates.add(rs);
2735                 }
2736             }
2737             return recvStates;
2738         }
2739     }
2740 
2741     /**
2742      * Get maximum number of sources that can be added to this Broadcast Sink
2743      *
2744      * @param sink Broadcast Sink device
2745      * @return maximum number of sources that can be added to this Broadcast Sink
2746      */
getMaximumSourceCapacity(BluetoothDevice sink)2747     int getMaximumSourceCapacity(BluetoothDevice sink) {
2748         log("getMaximumSourceCapacity: device = " + sink);
2749         BassClientStateMachine stateMachine = getOrCreateStateMachine(sink);
2750         if (stateMachine == null) {
2751             log("stateMachine is null");
2752             return 0;
2753         }
2754         return stateMachine.getMaximumSourceCapacity();
2755     }
2756 
isLocalBroadcast(int sourceAdvertisingSid)2757     private boolean isLocalBroadcast(int sourceAdvertisingSid) {
2758         LeAudioService leAudioService = mServiceFactory.getLeAudioService();
2759         if (leAudioService == null) {
2760             return false;
2761         }
2762 
2763         boolean wasFound =
2764                 leAudioService.getAllBroadcastMetadata().stream()
2765                         .anyMatch(
2766                                 meta -> {
2767                                     return meta.getSourceAdvertisingSid() == sourceAdvertisingSid;
2768                                 });
2769         log("isLocalBroadcast=" + wasFound);
2770         return wasFound;
2771     }
2772 
isLocalBroadcast(BluetoothLeBroadcastMetadata metaData)2773     boolean isLocalBroadcast(BluetoothLeBroadcastMetadata metaData) {
2774         if (metaData == null) {
2775             return false;
2776         }
2777 
2778         return isLocalBroadcast(metaData.getSourceAdvertisingSid());
2779     }
2780 
isLocalBroadcast(BluetoothLeBroadcastReceiveState receiveState)2781     boolean isLocalBroadcast(BluetoothLeBroadcastReceiveState receiveState) {
2782         if (receiveState == null) {
2783             return false;
2784         }
2785 
2786         return isLocalBroadcast(receiveState.getSourceAdvertisingSid());
2787     }
2788 
log(String msg)2789     static void log(String msg) {
2790         Log.d(TAG, msg);
2791     }
2792 
2793     private List<Pair<BluetoothLeBroadcastReceiveState, BluetoothDevice>>
getReceiveStateDevicePairs(int broadcastId)2794             getReceiveStateDevicePairs(int broadcastId) {
2795         List<Pair<BluetoothLeBroadcastReceiveState, BluetoothDevice>> list = new ArrayList<>();
2796 
2797         for (BluetoothDevice device : getConnectedDevices()) {
2798             for (BluetoothLeBroadcastReceiveState receiveState : getAllSources(device)) {
2799                 /* Check if local/last broadcast is the synced one. Invalid broadcast ID means
2800                  * that all receivers should be considered.
2801                  */
2802                 if ((broadcastId != BassConstants.INVALID_BROADCAST_ID)
2803                         && (receiveState.getBroadcastId() != broadcastId)) {
2804                     continue;
2805                 }
2806 
2807                 list.add(
2808                         new Pair<BluetoothLeBroadcastReceiveState, BluetoothDevice>(
2809                                 receiveState, device));
2810             }
2811         }
2812 
2813         return list;
2814     }
2815 
cancelPendingSourceOperations(int broadcastId)2816     private void cancelPendingSourceOperations(int broadcastId) {
2817         for (BluetoothDevice device : getConnectedDevices()) {
2818             synchronized (mStateMachines) {
2819                 BassClientStateMachine sm = getOrCreateStateMachine(device);
2820                 if (sm != null && sm.hasPendingSourceOperation(broadcastId)) {
2821                     Message message =
2822                             sm.obtainMessage(
2823                                     BassClientStateMachine.CANCEL_PENDING_SOURCE_OPERATION);
2824                     message.arg1 = broadcastId;
2825                     sm.sendMessage(message);
2826                 }
2827             }
2828         }
2829     }
2830 
stopSourceReceivers(int broadcastId)2831     private void stopSourceReceivers(int broadcastId) {
2832         List<Pair<BluetoothLeBroadcastReceiveState, BluetoothDevice>> sourcesToRemove =
2833                 getReceiveStateDevicePairs(broadcastId);
2834 
2835         for (Pair<BluetoothLeBroadcastReceiveState, BluetoothDevice> pair : sourcesToRemove) {
2836             removeSource(pair.second, pair.first.getSourceId());
2837         }
2838 
2839         /* There may be some pending add/modify source operations */
2840         cancelPendingSourceOperations(broadcastId);
2841     }
2842 
stopSourceReceivers(int broadcastId, boolean store)2843     private void stopSourceReceivers(int broadcastId, boolean store) {
2844         Log.d(TAG, "stopSourceReceivers(), broadcastId: " + broadcastId + ", store: " + store);
2845 
2846         if (store && !mPausedBroadcastSinks.isEmpty()) {
2847             Log.w(TAG, "stopSourceReceivers(), paused broadcast sinks are replaced");
2848             sEventLogger.logd(TAG, "Clear broadcast sinks paused cache");
2849             mPausedBroadcastSinks.clear();
2850         }
2851 
2852         Map<BluetoothDevice, Integer> sourcesToRemove = new HashMap<>();
2853 
2854         for (BluetoothDevice device : getConnectedDevices()) {
2855             for (BluetoothLeBroadcastReceiveState receiveState : getAllSources(device)) {
2856                 /* Check if local/last broadcast is the synced one. Invalid broadcast ID means
2857                  * that all receivers should be considered.
2858                  */
2859                 if ((broadcastId != BassConstants.INVALID_BROADCAST_ID)
2860                         && (receiveState.getBroadcastId() != broadcastId)) {
2861                     continue;
2862                 }
2863 
2864                 if (store && !mPausedBroadcastSinks.contains(device)) {
2865                     sEventLogger.logd(TAG, "Add broadcast sink to paused cache: " + device);
2866                     mPausedBroadcastSinks.add(device);
2867                 }
2868 
2869                 sourcesToRemove.put(device, receiveState.getSourceId());
2870             }
2871         }
2872 
2873         for (Map.Entry<BluetoothDevice, Integer> entry : sourcesToRemove.entrySet()) {
2874             removeSource(entry.getKey(), entry.getValue());
2875         }
2876     }
2877 
isAllowedToAddSource()2878     private boolean isAllowedToAddSource() {
2879         if (leaudioBroadcastAudioHandoverPolicies()) {
2880             /* Check if should wait for status update */
2881             if (mUnicastSourceStreamStatus.isEmpty()) {
2882                 /* Assistant was not active, inform about activation */
2883                 if (!mIsAssistantActive) {
2884                     mIsAssistantActive = true;
2885 
2886                     LeAudioService leAudioService = mServiceFactory.getLeAudioService();
2887                     if (leAudioService != null) {
2888                         leAudioService.activeBroadcastAssistantNotification(true);
2889                     }
2890                 }
2891 
2892                 return false;
2893             }
2894 
2895             return mUnicastSourceStreamStatus.get() == STATUS_LOCAL_STREAM_SUSPENDED;
2896         }
2897 
2898         /* Don't block if this is not a handover case */
2899         return true;
2900     }
2901 
2902     /** Return true if there is any non primary device receiving broadcast */
isAudioSharingModeOn(Integer broadcastId)2903     private boolean isAudioSharingModeOn(Integer broadcastId) {
2904         if (mLocalBroadcastReceivers == null) {
2905             Log.w(TAG, "isAudioSharingModeOn: Local Broadcaster Receivers is not initialized");
2906             return false;
2907         }
2908 
2909         HashSet<BluetoothDevice> devices = mLocalBroadcastReceivers.get(broadcastId);
2910         if (devices == null) {
2911             Log.w(TAG, "isAudioSharingModeOn: No receivers receiving broadcast: " + broadcastId);
2912             return false;
2913         }
2914 
2915         LeAudioService leAudioService = mServiceFactory.getLeAudioService();
2916         if (leAudioService == null) {
2917             Log.d(TAG, "isAudioSharingModeOn: No available LeAudioService");
2918             return false;
2919         }
2920 
2921         return devices.stream().anyMatch(d -> !leAudioService.isPrimaryDevice(d));
2922     }
2923 
2924     /** Handle disconnection of potential broadcast sinks */
handleDeviceDisconnection(BluetoothDevice sink, boolean isIntentional)2925     public void handleDeviceDisconnection(BluetoothDevice sink, boolean isIntentional) {
2926         LeAudioService leAudioService = mServiceFactory.getLeAudioService();
2927         if (leAudioService == null) {
2928             Log.d(TAG, "BluetoothLeBroadcastReceiveState: No available LeAudioService");
2929             return;
2930         }
2931 
2932         for (Map.Entry<Integer, HashSet<BluetoothDevice>> entry :
2933                 mLocalBroadcastReceivers.entrySet()) {
2934             Integer broadcastId = entry.getKey();
2935             HashSet<BluetoothDevice> devices = entry.getValue();
2936 
2937             /* If somehow there is a non playing broadcast, let's remove it */
2938             if (!leAudioService.isPlaying(broadcastId)) {
2939                 Log.w(TAG, "Non playing broadcast remove from receivers list");
2940                 mLocalBroadcastReceivers.remove(broadcastId);
2941                 continue;
2942             }
2943 
2944             if (isIntentional) {
2945                 /* Check if disconnecting device participated in this broadcast reception */
2946                 if (!devices.remove(sink)) {
2947                     continue;
2948                 }
2949 
2950                 mBroadcastMetadataMap.remove(sink);
2951 
2952                 /* Check if there is any other primary device receiving this broadcast */
2953                 if (devices.stream()
2954                         .anyMatch(
2955                                 d ->
2956                                         ((getConnectionState(d) == BluetoothProfile.STATE_CONNECTED)
2957                                                 && leAudioService.isPrimaryDevice(d)))) {
2958                     continue;
2959                 }
2960 
2961                 Log.d(
2962                         TAG,
2963                         "handleIntendedDeviceDisconnection: No more potential broadcast "
2964                                 + "(broadcast ID: "
2965                                 + broadcastId
2966                                 + ") receivers - stopping broadcast");
2967                 mLocalBroadcastReceivers.remove(broadcastId);
2968                 leAudioService.stopBroadcast(broadcastId);
2969             } else {
2970                 /* Unintentional disconnection of primary device in private broadcast mode */
2971                 if (!isAudioSharingModeOn(broadcastId)
2972                         && !devices.stream()
2973                                 .anyMatch(
2974                                         d ->
2975                                                 !d.equals(sink)
2976                                                         && (getConnectionState(d)
2977                                                                 == BluetoothProfile
2978                                                                         .STATE_CONNECTED))) {
2979                     mLocalBroadcastReceivers.remove(broadcastId);
2980                     leAudioService.stopBroadcast(broadcastId);
2981                     continue;
2982                 }
2983 
2984                 /* Unintentional disconnection of primary/secondary in broadcast sharing mode */
2985                 if (devices.stream()
2986                         .anyMatch(
2987                                 d ->
2988                                         !d.equals(sink)
2989                                                 && (getConnectionState(d)
2990                                                         == BluetoothProfile.STATE_CONNECTED))) {
2991                     continue;
2992                 } else {
2993                     Log.d(
2994                             TAG,
2995                             "handleUnintendedDeviceDisconnection: No more potential broadcast "
2996                                     + "(broadcast ID: "
2997                                     + broadcastId
2998                                     + ") receivers - stopping broadcast");
2999                     mDialingOutTimeoutEvent = new DialingOutTimeoutEvent(broadcastId);
3000                     mHandler.postDelayed(mDialingOutTimeoutEvent, DIALING_OUT_TIMEOUT_MS);
3001                 }
3002             }
3003         }
3004     }
3005 
3006     /** Cache suspending sources */
cacheSuspendingSources(int broadcastId)3007     public void cacheSuspendingSources(int broadcastId) {
3008         sEventLogger.logd(TAG, "Cache suspending sources: " + broadcastId);
3009         List<Pair<BluetoothLeBroadcastReceiveState, BluetoothDevice>> sourcesToCache =
3010                 getReceiveStateDevicePairs(broadcastId);
3011 
3012         if (!mPausedBroadcastSinks.isEmpty()) {
3013             Log.w(TAG, "cacheSuspendingSources(), paused broadcast sinks are replaced");
3014             sEventLogger.logd(TAG, "Clear broadcast sinks paused cache");
3015             mPausedBroadcastSinks.clear();
3016         }
3017 
3018         for (Pair<BluetoothLeBroadcastReceiveState, BluetoothDevice> pair : sourcesToCache) {
3019             mPausedBroadcastSinks.add(pair.second);
3020         }
3021     }
3022 
3023     /** Request receivers to suspend broadcast sources synchronization */
suspendReceiversSourceSynchronization(int broadcastId)3024     public void suspendReceiversSourceSynchronization(int broadcastId) {
3025         sEventLogger.logd(TAG, "Suspend receivers source synchronization: " + broadcastId);
3026         stopSourceReceivers(broadcastId, true);
3027     }
3028 
3029     /** Request all receivers to suspend broadcast sources synchronization */
suspendAllReceiversSourceSynchronization()3030     public void suspendAllReceiversSourceSynchronization() {
3031         sEventLogger.logd(TAG, "Suspend all receivers source synchronization");
3032         stopSourceReceivers(BassConstants.INVALID_BROADCAST_ID, true);
3033     }
3034 
3035     /** Request receivers to stop broadcast sources synchronization and remove them */
stopReceiversSourceSynchronization(int broadcastId)3036     public void stopReceiversSourceSynchronization(int broadcastId) {
3037         sEventLogger.logd(TAG, "Stop receivers source synchronization: " + broadcastId);
3038         if (leaudioBroadcastAssistantPeripheralEntrustment()) {
3039             stopSourceReceivers(broadcastId);
3040         } else {
3041             stopSourceReceivers(broadcastId, false);
3042         }
3043     }
3044 
3045     /** Request receivers to resume broadcast source synchronization */
resumeReceiversSourceSynchronization()3046     public void resumeReceiversSourceSynchronization() {
3047         sEventLogger.logd(TAG, "Resume receivers source synchronization");
3048 
3049         while (!mPausedBroadcastSinks.isEmpty()) {
3050             BluetoothDevice sink = mPausedBroadcastSinks.remove();
3051             sEventLogger.logd(TAG, "Remove broadcast sink from paused cache: " + sink);
3052             BluetoothLeBroadcastMetadata metadata = mBroadcastMetadataMap.get(sink);
3053 
3054             if (leaudioBroadcastAssistantPeripheralEntrustment()) {
3055                 if (metadata == null) {
3056                     Log.w(
3057                             TAG,
3058                             "resumeReceiversSourceSynchronization: failed to get metadata to resume"
3059                                     + " sink: "
3060                                     + sink);
3061                     continue;
3062                 }
3063 
3064                 // For each device, find the source ID having this broadcast ID
3065                 BassClientStateMachine stateMachine = getOrCreateStateMachine(sink);
3066                 List<BluetoothLeBroadcastReceiveState> sources = stateMachine.getAllSources();
3067                 Optional<BluetoothLeBroadcastReceiveState> receiveState =
3068                         sources.stream()
3069                                 .filter(e -> e.getBroadcastId() == metadata.getBroadcastId())
3070                                 .findAny();
3071 
3072                 if (receiveState.isPresent()) {
3073                     /* Update metadata for sink device */
3074                     mBroadcastMetadataMap.put(sink, metadata);
3075 
3076                     int sourceId = receiveState.get().getSourceId();
3077                     int statusCode =
3078                             areValidParametersToModifySource(
3079                                     metadata, stateMachine, sourceId, sink);
3080 
3081                     if (statusCode != BluetoothStatusCodes.SUCCESS) {
3082                         mCallbacks.notifySourceModifyFailed(sink, sourceId, statusCode);
3083                         continue;
3084                     }
3085 
3086                     sEventLogger.logd(
3087                             TAG,
3088                             "Modify Broadcast Source (resume): "
3089                                     + ("device: " + sink)
3090                                     + ("sourceId: " + sourceId)
3091                                     + (", updatedBroadcastId: " + metadata.getBroadcastId())
3092                                     + (", updatedBroadcastName: " + metadata.getBroadcastName()));
3093                     Message message =
3094                             stateMachine.obtainMessage(BassClientStateMachine.UPDATE_BCAST_SOURCE);
3095                     message.arg1 = sourceId;
3096                     message.arg2 =
3097                             DeviceConfig.getBoolean(
3098                                             DeviceConfig.NAMESPACE_BLUETOOTH,
3099                                             "persist.vendor.service.bt.defNoPAS",
3100                                             true)
3101                                     ? BassConstants.PA_SYNC_PAST_AVAILABLE
3102                                     : BassConstants.PA_SYNC_PAST_NOT_AVAILABLE;
3103                     message.obj = metadata;
3104                     stateMachine.sendMessage(message);
3105                 } else {
3106                     addSource(sink, metadata, false);
3107                 }
3108             } else {
3109                 if (metadata != null) {
3110                     addSource(sink, metadata, false);
3111                 } else {
3112                     Log.w(
3113                             TAG,
3114                             "resumeReceiversSourceSynchronization: failed to get metadata to resume"
3115                                     + " sink: "
3116                                     + sink);
3117                 }
3118             }
3119         }
3120     }
3121 
3122     /** Handle Unicast source stream status change */
handleUnicastSourceStreamStatusChange(int status)3123     public void handleUnicastSourceStreamStatusChange(int status) {
3124         mUnicastSourceStreamStatus = Optional.of(status);
3125 
3126         if (status == STATUS_LOCAL_STREAM_REQUESTED) {
3127             if (areReceiversReceivingOnlyExternalBroadcast(getConnectedDevices())) {
3128                 if (leaudioBroadcastAssistantPeripheralEntrustment()) {
3129                     cacheSuspendingSources(BassConstants.INVALID_BROADCAST_ID);
3130                 } else {
3131                     suspendAllReceiversSourceSynchronization();
3132                 }
3133             }
3134         } else if (status == STATUS_LOCAL_STREAM_SUSPENDED) {
3135             /* Resume paused receivers if there are some */
3136             if (!mPausedBroadcastSinks.isEmpty()) {
3137                 resumeReceiversSourceSynchronization();
3138             }
3139 
3140             if (!leaudioBroadcastAssistantPeripheralEntrustment()) {
3141                 /* Add pending sources if there are some */
3142                 while (!mPendingAddSources.isEmpty()) {
3143                     AddSourceData addSourceData = mPendingAddSources.pop();
3144 
3145                     addSource(
3146                             addSourceData.mSink,
3147                             addSourceData.mSourceMetadata,
3148                             addSourceData.mIsGroupOp);
3149                 }
3150             }
3151         } else if (status == STATUS_LOCAL_STREAM_STREAMING) {
3152             Log.d(TAG, "Ignore STREAMING source status");
3153         } else if (status == STATUS_LOCAL_STREAM_REQUESTED_NO_CONTEXT_VALIDATE) {
3154             suspendAllReceiversSourceSynchronization();
3155         }
3156     }
3157 
3158     /** Check if any sink receivers are receiving broadcast stream */
isAnyReceiverReceivingBroadcast(List<BluetoothDevice> devices)3159     public boolean isAnyReceiverReceivingBroadcast(List<BluetoothDevice> devices) {
3160         for (BluetoothDevice device : devices) {
3161             for (BluetoothLeBroadcastReceiveState receiveState : getAllSources(device)) {
3162                 for (int i = 0; i < receiveState.getNumSubgroups(); i++) {
3163                     Long syncState = receiveState.getBisSyncState().get(i);
3164                     /* Synced to BIS */
3165                     if (syncState != BassConstants.BIS_SYNC_NOT_SYNC_TO_BIS
3166                             && syncState != BassConstants.BIS_SYNC_FAILED_SYNC_TO_BIG) {
3167                         return true;
3168                     }
3169                 }
3170             }
3171         }
3172 
3173         return false;
3174     }
3175 
3176     /** Check if any sink receivers are receiving broadcast stream */
areReceiversReceivingOnlyExternalBroadcast(List<BluetoothDevice> devices)3177     public boolean areReceiversReceivingOnlyExternalBroadcast(List<BluetoothDevice> devices) {
3178         boolean isReceivingExternalBroadcast = false;
3179 
3180         for (BluetoothDevice device : devices) {
3181             for (BluetoothLeBroadcastReceiveState receiveState : getAllSources(device)) {
3182                 for (int i = 0; i < receiveState.getNumSubgroups(); i++) {
3183                     Long syncState = receiveState.getBisSyncState().get(i);
3184                     /* Synced to BIS */
3185                     if (syncState != BassConstants.BIS_SYNC_NOT_SYNC_TO_BIS
3186                             && syncState != BassConstants.BIS_SYNC_FAILED_SYNC_TO_BIG) {
3187                         if (isLocalBroadcast(receiveState)) {
3188                             return false;
3189                         } else {
3190                             isReceivingExternalBroadcast = true;
3191                         }
3192                     }
3193 
3194                 }
3195             }
3196         }
3197 
3198         return isReceivingExternalBroadcast;
3199     }
3200 
3201     /** Get the active broadcast sink devices receiving broadcast stream */
getActiveBroadcastSinks()3202     public List<BluetoothDevice> getActiveBroadcastSinks() {
3203         List<BluetoothDevice> activeSinks = new ArrayList<>();
3204 
3205         for (BluetoothDevice device : getConnectedDevices()) {
3206             // Check if any device's source in active sync state
3207             if (getAllSources(device).stream()
3208                     .anyMatch(
3209                             receiveState ->
3210                                     (receiveState.getBisSyncState().stream()
3211                                             .anyMatch(
3212                                                     syncState ->
3213                                                             syncState
3214                                                                             != BassConstants
3215                                                                                     .BIS_SYNC_NOT_SYNC_TO_BIS
3216                                                                     && syncState
3217                                                                             != BassConstants
3218                                                                                     .BIS_SYNC_FAILED_SYNC_TO_BIG)))) {
3219                 activeSinks.add(device);
3220             }
3221         }
3222         return activeSinks;
3223     }
3224 
3225     /** Handle broadcast state changed */
notifyBroadcastStateChanged(int state, int broadcastId)3226     public void notifyBroadcastStateChanged(int state, int broadcastId) {
3227         switch (state) {
3228             case BROADCAST_STATE_STOPPED:
3229                 if (mLocalBroadcastReceivers == null) {
3230                     Log.e(TAG, "notifyBroadcastStateChanged: mLocalBroadcastReceivers is invalid");
3231                     break;
3232                 }
3233 
3234                 if (mLocalBroadcastReceivers.remove(broadcastId) != null) {
3235                     sEventLogger.logd(TAG, "Broadcast ID: " + broadcastId + ", stopped");
3236                 }
3237                 break;
3238             case BROADCAST_STATE_CONFIGURING:
3239             case BROADCAST_STATE_PAUSED:
3240             case BROADCAST_STATE_STOPPING:
3241             case BROADCAST_STATE_STREAMING:
3242             default:
3243                 break;
3244         }
3245     }
3246 
3247     /** Callback handler */
3248     static class Callbacks extends Handler {
3249         private static final int MSG_SEARCH_STARTED = 1;
3250         private static final int MSG_SEARCH_STARTED_FAILED = 2;
3251         private static final int MSG_SEARCH_STOPPED = 3;
3252         private static final int MSG_SEARCH_STOPPED_FAILED = 4;
3253         private static final int MSG_SOURCE_FOUND = 5;
3254         private static final int MSG_SOURCE_ADDED = 6;
3255         private static final int MSG_SOURCE_ADDED_FAILED = 7;
3256         private static final int MSG_SOURCE_MODIFIED = 8;
3257         private static final int MSG_SOURCE_MODIFIED_FAILED = 9;
3258         private static final int MSG_SOURCE_REMOVED = 10;
3259         private static final int MSG_SOURCE_REMOVED_FAILED = 11;
3260         private static final int MSG_RECEIVESTATE_CHANGED = 12;
3261         private static final int MSG_SOURCE_LOST = 13;
3262 
3263         private final RemoteCallbackList<IBluetoothLeBroadcastAssistantCallback> mCallbacks =
3264                 new RemoteCallbackList<>();
3265 
Callbacks(Looper looper)3266         Callbacks(Looper looper) {
3267             super(looper);
3268         }
3269 
register(IBluetoothLeBroadcastAssistantCallback callback)3270         public void register(IBluetoothLeBroadcastAssistantCallback callback) {
3271             mCallbacks.register(callback);
3272         }
3273 
unregister(IBluetoothLeBroadcastAssistantCallback callback)3274         public void unregister(IBluetoothLeBroadcastAssistantCallback callback) {
3275             mCallbacks.unregister(callback);
3276         }
3277 
checkForPendingGroupOpRequest(Message msg)3278         private void checkForPendingGroupOpRequest(Message msg) {
3279             if (sService == null) {
3280                 Log.e(TAG, "Service is null");
3281                 return;
3282             }
3283 
3284             final int reason = msg.arg1;
3285             BluetoothDevice sink;
3286 
3287             switch (msg.what) {
3288                 case MSG_SOURCE_ADDED:
3289                 case MSG_SOURCE_ADDED_FAILED:
3290                     ObjParams param = (ObjParams) msg.obj;
3291                     sink = (BluetoothDevice) param.mObj1;
3292                     sService.checkForPendingGroupOpRequest(
3293                             sink, reason, BassClientStateMachine.ADD_BCAST_SOURCE, param.mObj2);
3294                     break;
3295                 case MSG_SOURCE_REMOVED:
3296                 case MSG_SOURCE_REMOVED_FAILED:
3297                     sink = (BluetoothDevice) msg.obj;
3298                     sService.checkForPendingGroupOpRequest(
3299                             sink,
3300                             reason,
3301                             BassClientStateMachine.REMOVE_BCAST_SOURCE,
3302                             Integer.valueOf(msg.arg2));
3303                     break;
3304                 default:
3305                     break;
3306             }
3307         }
3308 
3309         @Override
handleMessage(Message msg)3310         public void handleMessage(Message msg) {
3311             checkForPendingGroupOpRequest(msg);
3312             final int n = mCallbacks.beginBroadcast();
3313             for (int i = 0; i < n; i++) {
3314                 final IBluetoothLeBroadcastAssistantCallback callback =
3315                         mCallbacks.getBroadcastItem(i);
3316                 try {
3317                     invokeCallback(callback, msg);
3318                 } catch (RemoteException e) {
3319                     continue;
3320                 }
3321             }
3322             mCallbacks.finishBroadcast();
3323         }
3324 
3325         private static class ObjParams {
3326             Object mObj1;
3327             Object mObj2;
3328 
ObjParams(Object o1, Object o2)3329             ObjParams(Object o1, Object o2) {
3330                 mObj1 = o1;
3331                 mObj2 = o2;
3332             }
3333         }
3334 
invokeCallback(IBluetoothLeBroadcastAssistantCallback callback, Message msg)3335         private void invokeCallback(IBluetoothLeBroadcastAssistantCallback callback, Message msg)
3336                 throws RemoteException {
3337             final int reason = msg.arg1;
3338             final int sourceId = msg.arg2;
3339             ObjParams param;
3340             BluetoothDevice sink;
3341 
3342             switch (msg.what) {
3343                 case MSG_SEARCH_STARTED:
3344                     callback.onSearchStarted(reason);
3345                     break;
3346                 case MSG_SEARCH_STARTED_FAILED:
3347                     callback.onSearchStartFailed(reason);
3348                     break;
3349                 case MSG_SEARCH_STOPPED:
3350                     callback.onSearchStopped(reason);
3351                     break;
3352                 case MSG_SEARCH_STOPPED_FAILED:
3353                     callback.onSearchStopFailed(reason);
3354                     break;
3355                 case MSG_SOURCE_FOUND:
3356                     callback.onSourceFound((BluetoothLeBroadcastMetadata) msg.obj);
3357                     break;
3358                 case MSG_SOURCE_ADDED:
3359                     param = (ObjParams) msg.obj;
3360                     sink = (BluetoothDevice) param.mObj1;
3361                     callback.onSourceAdded(sink, sourceId, reason);
3362                     break;
3363                 case MSG_SOURCE_ADDED_FAILED:
3364                     param = (ObjParams) msg.obj;
3365                     sink = (BluetoothDevice) param.mObj1;
3366                     BluetoothLeBroadcastMetadata metadata =
3367                             (BluetoothLeBroadcastMetadata) param.mObj2;
3368                     callback.onSourceAddFailed(sink, metadata, reason);
3369                     break;
3370                 case MSG_SOURCE_MODIFIED:
3371                     callback.onSourceModified((BluetoothDevice) msg.obj, sourceId, reason);
3372                     break;
3373                 case MSG_SOURCE_MODIFIED_FAILED:
3374                     callback.onSourceModifyFailed((BluetoothDevice) msg.obj, sourceId, reason);
3375                     break;
3376                 case MSG_SOURCE_REMOVED:
3377                     sink = (BluetoothDevice) msg.obj;
3378                     callback.onSourceRemoved(sink, sourceId, reason);
3379                     break;
3380                 case MSG_SOURCE_REMOVED_FAILED:
3381                     sink = (BluetoothDevice) msg.obj;
3382                     callback.onSourceRemoveFailed(sink, sourceId, reason);
3383                     break;
3384                 case MSG_RECEIVESTATE_CHANGED:
3385                     param = (ObjParams) msg.obj;
3386                     sink = (BluetoothDevice) param.mObj1;
3387                     BluetoothLeBroadcastReceiveState state =
3388                             (BluetoothLeBroadcastReceiveState) param.mObj2;
3389                     callback.onReceiveStateChanged(sink, sourceId, state);
3390                     break;
3391                 case MSG_SOURCE_LOST:
3392                     callback.onSourceLost(sourceId);
3393                     break;
3394                 default:
3395                     Log.e(TAG, "Invalid msg: " + msg.what);
3396                     break;
3397             }
3398         }
3399 
notifySearchStarted(int reason)3400         void notifySearchStarted(int reason) {
3401             sEventLogger.logd(TAG, "notifySearchStarted: reason: " + reason);
3402             obtainMessage(MSG_SEARCH_STARTED, reason, 0).sendToTarget();
3403         }
3404 
notifySearchStartFailed(int reason)3405         void notifySearchStartFailed(int reason) {
3406             sEventLogger.loge(TAG, "notifySearchStartFailed: reason: " + reason);
3407             obtainMessage(MSG_SEARCH_STARTED_FAILED, reason, 0).sendToTarget();
3408         }
3409 
notifySearchStopped(int reason)3410         void notifySearchStopped(int reason) {
3411             sEventLogger.logd(TAG, "notifySearchStopped: reason: " + reason);
3412             obtainMessage(MSG_SEARCH_STOPPED, reason, 0).sendToTarget();
3413         }
3414 
notifySearchStopFailed(int reason)3415         void notifySearchStopFailed(int reason) {
3416             sEventLogger.loge(TAG, "notifySearchStopFailed: reason: " + reason);
3417             obtainMessage(MSG_SEARCH_STOPPED_FAILED, reason, 0).sendToTarget();
3418         }
3419 
notifySourceFound(BluetoothLeBroadcastMetadata source)3420         void notifySourceFound(BluetoothLeBroadcastMetadata source) {
3421             sEventLogger.logd(
3422                     TAG,
3423                     "invokeCallback: MSG_SOURCE_FOUND"
3424                             + ", source: "
3425                             + source.getSourceDevice()
3426                             + ", broadcastId: "
3427                             + source.getBroadcastId()
3428                             + ", broadcastName: "
3429                             + source.getBroadcastName()
3430                             + ", isPublic: "
3431                             + source.isPublicBroadcast()
3432                             + ", isEncrypted: "
3433                             + source.isEncrypted());
3434             obtainMessage(MSG_SOURCE_FOUND, 0, 0, source).sendToTarget();
3435         }
3436 
notifySourceAdded( BluetoothDevice sink, BluetoothLeBroadcastReceiveState recvState, int reason)3437         void notifySourceAdded(
3438                 BluetoothDevice sink, BluetoothLeBroadcastReceiveState recvState, int reason) {
3439             sService.localNotifySourceAdded(sink, recvState);
3440 
3441             sEventLogger.logd(
3442                     TAG,
3443                     "notifySourceAdded: "
3444                             + "sink: "
3445                             + sink
3446                             + ", sourceId: "
3447                             + recvState.getSourceId()
3448                             + ", reason: "
3449                             + reason);
3450 
3451             ObjParams param = new ObjParams(sink, recvState);
3452             obtainMessage(MSG_SOURCE_ADDED, reason, recvState.getSourceId(), param).sendToTarget();
3453         }
3454 
notifySourceAddFailed( BluetoothDevice sink, BluetoothLeBroadcastMetadata source, int reason)3455         void notifySourceAddFailed(
3456                 BluetoothDevice sink, BluetoothLeBroadcastMetadata source, int reason) {
3457             sEventLogger.loge(
3458                     TAG,
3459                     "notifySourceAddFailed: sink: "
3460                             + sink
3461                             + ", source: "
3462                             + source
3463                             + ", reason: "
3464                             + reason);
3465             ObjParams param = new ObjParams(sink, source);
3466             obtainMessage(MSG_SOURCE_ADDED_FAILED, reason, 0, param).sendToTarget();
3467         }
3468 
notifySourceModified(BluetoothDevice sink, int sourceId, int reason)3469         void notifySourceModified(BluetoothDevice sink, int sourceId, int reason) {
3470             sEventLogger.logd(
3471                     TAG,
3472                     "notifySourceModified: "
3473                             + "sink: "
3474                             + sink
3475                             + ", sourceId: "
3476                             + sourceId
3477                             + ", reason: "
3478                             + reason);
3479             obtainMessage(MSG_SOURCE_MODIFIED, reason, sourceId, sink).sendToTarget();
3480         }
3481 
notifySourceModifyFailed(BluetoothDevice sink, int sourceId, int reason)3482         void notifySourceModifyFailed(BluetoothDevice sink, int sourceId, int reason) {
3483             sEventLogger.loge(
3484                     TAG,
3485                     "notifySourceModifyFailed: sink: "
3486                             + sink
3487                             + ", sourceId: "
3488                             + sourceId
3489                             + ", reason: "
3490                             + reason);
3491             obtainMessage(MSG_SOURCE_MODIFIED_FAILED, reason, sourceId, sink).sendToTarget();
3492         }
3493 
notifySourceRemoved(BluetoothDevice sink, int sourceId, int reason)3494         void notifySourceRemoved(BluetoothDevice sink, int sourceId, int reason) {
3495             sEventLogger.logd(
3496                     TAG,
3497                     "notifySourceRemoved: "
3498                             + "sink: "
3499                             + sink
3500                             + ", sourceId: "
3501                             + sourceId
3502                             + ", reason: "
3503                             + reason);
3504             obtainMessage(MSG_SOURCE_REMOVED, reason, sourceId, sink).sendToTarget();
3505         }
3506 
notifySourceRemoveFailed(BluetoothDevice sink, int sourceId, int reason)3507         void notifySourceRemoveFailed(BluetoothDevice sink, int sourceId, int reason) {
3508             sEventLogger.loge(
3509                     TAG,
3510                     "notifySourceRemoveFailed: "
3511                             + "sink: "
3512                             + sink
3513                             + ", sourceId: "
3514                             + sourceId
3515                             + ", reason: "
3516                             + reason);
3517             obtainMessage(MSG_SOURCE_REMOVED_FAILED, reason, sourceId, sink).sendToTarget();
3518         }
3519 
notifyReceiveStateChanged( BluetoothDevice sink, int sourceId, BluetoothLeBroadcastReceiveState state)3520         void notifyReceiveStateChanged(
3521                 BluetoothDevice sink, int sourceId, BluetoothLeBroadcastReceiveState state) {
3522             ObjParams param = new ObjParams(sink, state);
3523 
3524             sService.localNotifyReceiveStateChanged(sink);
3525 
3526             String subgroupState = " / SUB GROUPS: ";
3527             for (int i = 0; i < state.getNumSubgroups(); i++) {
3528                 subgroupState += "IDX: " + i + ", SYNC: " + state.getBisSyncState().get(i);
3529             }
3530 
3531             sEventLogger.logd(
3532                     TAG,
3533                     "notifyReceiveStateChanged: "
3534                             + "sink: "
3535                             + sink
3536                             + ", state: SRC ID: "
3537                             + state.getSourceId()
3538                             + " / ADDR TYPE: "
3539                             + state.getSourceAddressType()
3540                             + " / SRC DEV: "
3541                             + state.getSourceDevice()
3542                             + " / ADV SID: "
3543                             + state.getSourceAdvertisingSid()
3544                             + " / BID: "
3545                             + state.getBroadcastId()
3546                             + " / PA STATE: "
3547                             + state.getPaSyncState()
3548                             + " / BENC STATE: "
3549                             + state.getBigEncryptionState()
3550                             + " / BAD CODE: "
3551                             + Arrays.toString(state.getBadCode())
3552                             + subgroupState);
3553             obtainMessage(MSG_RECEIVESTATE_CHANGED, 0, sourceId, param).sendToTarget();
3554         }
3555 
notifySourceLost(int broadcastId)3556         void notifySourceLost(int broadcastId) {
3557             sEventLogger.logd(TAG, "notifySourceLost: broadcastId: " + broadcastId);
3558             obtainMessage(MSG_SOURCE_LOST, 0, broadcastId).sendToTarget();
3559         }
3560     }
3561 
3562     @Override
dump(StringBuilder sb)3563     public void dump(StringBuilder sb) {
3564         super.dump(sb);
3565 
3566         sb.append("Broadcast Assistant Service instance:\n");
3567 
3568         /* Dump first connected state machines */
3569         for (Map.Entry<BluetoothDevice, BassClientStateMachine> entry : mStateMachines.entrySet()) {
3570             BassClientStateMachine sm = entry.getValue();
3571             if (sm.getConnectionState() == BluetoothProfile.STATE_CONNECTED) {
3572                 sm.dump(sb);
3573                 sb.append("\n\n");
3574             }
3575         }
3576 
3577         /* Dump at least all other than connected state machines */
3578         for (Map.Entry<BluetoothDevice, BassClientStateMachine> entry : mStateMachines.entrySet()) {
3579             BassClientStateMachine sm = entry.getValue();
3580             if (sm.getConnectionState() != BluetoothProfile.STATE_CONNECTED) {
3581                 sm.dump(sb);
3582             }
3583         }
3584 
3585         sb.append("\n\n");
3586         sEventLogger.dump(sb);
3587         sb.append("\n");
3588     }
3589 
3590     /** Binder object: must be a static class or memory leak may occur */
3591     @VisibleForTesting
3592     static class BluetoothLeBroadcastAssistantBinder extends IBluetoothLeBroadcastAssistant.Stub
3593             implements IProfileServiceBinder {
3594         BassClientService mService;
3595 
getService()3596         private BassClientService getService() {
3597             if (Utils.isInstrumentationTestMode()) {
3598                 return mService;
3599             }
3600             if (!Utils.checkServiceAvailable(mService, TAG)
3601                     || !Utils.checkCallerIsSystemOrActiveOrManagedUser(mService, TAG)) {
3602                 return null;
3603             }
3604             return mService;
3605         }
3606 
BluetoothLeBroadcastAssistantBinder(BassClientService svc)3607         BluetoothLeBroadcastAssistantBinder(BassClientService svc) {
3608             mService = svc;
3609         }
3610 
3611         @Override
cleanup()3612         public void cleanup() {
3613             mService = null;
3614         }
3615 
3616         @Override
getConnectionState(BluetoothDevice sink)3617         public int getConnectionState(BluetoothDevice sink) {
3618             try {
3619                 BassClientService service = getService();
3620                 if (service == null) {
3621                     Log.e(TAG, "Service is null");
3622                     return BluetoothProfile.STATE_DISCONNECTED;
3623                 }
3624                 return service.getConnectionState(sink);
3625             } catch (RuntimeException e) {
3626                 Log.e(TAG, "Exception happened", e);
3627                 return BluetoothProfile.STATE_DISCONNECTED;
3628             }
3629         }
3630 
3631         @Override
getDevicesMatchingConnectionStates(int[] states)3632         public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
3633             try {
3634                 BassClientService service = getService();
3635                 if (service == null) {
3636                     Log.e(TAG, "Service is null");
3637                     return Collections.emptyList();
3638                 }
3639                 return service.getDevicesMatchingConnectionStates(states);
3640             } catch (RuntimeException e) {
3641                 Log.e(TAG, "Exception happened", e);
3642                 return Collections.emptyList();
3643             }
3644         }
3645 
3646         @Override
getConnectedDevices()3647         public List<BluetoothDevice> getConnectedDevices() {
3648             try {
3649                 BassClientService service = getService();
3650                 if (service == null) {
3651                     Log.e(TAG, "Service is null");
3652                     return Collections.emptyList();
3653                 }
3654                 return service.getConnectedDevices();
3655             } catch (RuntimeException e) {
3656                 Log.e(TAG, "Exception happened", e);
3657                 return Collections.emptyList();
3658             }
3659         }
3660 
3661         @Override
setConnectionPolicy(BluetoothDevice device, int connectionPolicy)3662         public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
3663             try {
3664                 BassClientService service = getService();
3665                 if (service == null) {
3666                     Log.e(TAG, "Service is null");
3667                     return false;
3668                 }
3669                 mService.enforceCallingOrSelfPermission(
3670                         BLUETOOTH_CONNECT, "Need BLUETOOTH_CONNECT permission");
3671                 return service.setConnectionPolicy(device, connectionPolicy);
3672             } catch (RuntimeException e) {
3673                 Log.e(TAG, "Exception happened", e);
3674                 return false;
3675             }
3676         }
3677 
3678         @Override
getConnectionPolicy(BluetoothDevice device)3679         public int getConnectionPolicy(BluetoothDevice device) {
3680             try {
3681                 BassClientService service = getService();
3682                 if (service == null) {
3683                     Log.e(TAG, "Service is null");
3684                     return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
3685                 }
3686                 mService.enforceCallingOrSelfPermission(
3687                         BLUETOOTH_CONNECT, "Need BLUETOOTH_CONNECT permission");
3688                 return service.getConnectionPolicy(device);
3689             } catch (RuntimeException e) {
3690                 Log.e(TAG, "Exception happened", e);
3691                 return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
3692             }
3693         }
3694 
3695         @Override
registerCallback(IBluetoothLeBroadcastAssistantCallback cb)3696         public void registerCallback(IBluetoothLeBroadcastAssistantCallback cb) {
3697             try {
3698                 BassClientService service = getService();
3699                 if (service == null) {
3700                     Log.e(TAG, "Service is null");
3701                     return;
3702                 }
3703                 enforceBluetoothPrivilegedPermission(service);
3704                 service.registerCallback(cb);
3705             } catch (RuntimeException e) {
3706                 Log.e(TAG, "Exception happened", e);
3707             }
3708         }
3709 
3710         @Override
unregisterCallback(IBluetoothLeBroadcastAssistantCallback cb)3711         public void unregisterCallback(IBluetoothLeBroadcastAssistantCallback cb) {
3712             try {
3713                 BassClientService service = getService();
3714                 if (service == null) {
3715                     Log.e(TAG, "Service is null");
3716                     return;
3717                 }
3718                 enforceBluetoothPrivilegedPermission(service);
3719                 service.unregisterCallback(cb);
3720             } catch (RuntimeException e) {
3721                 Log.e(TAG, "Exception happened", e);
3722             }
3723         }
3724 
3725         @Override
startSearchingForSources(List<ScanFilter> filters)3726         public void startSearchingForSources(List<ScanFilter> filters) {
3727             try {
3728                 BassClientService service = getService();
3729                 if (service == null) {
3730                     Log.e(TAG, "Service is null");
3731                     return;
3732                 }
3733                 enforceBluetoothPrivilegedPermission(service);
3734                 service.startSearchingForSources(filters);
3735             } catch (RuntimeException e) {
3736                 Log.e(TAG, "Exception happened", e);
3737             }
3738         }
3739 
3740         @Override
stopSearchingForSources()3741         public void stopSearchingForSources() {
3742             try {
3743                 BassClientService service = getService();
3744                 if (service == null) {
3745                     Log.e(TAG, "Service is null");
3746                     return;
3747                 }
3748                 enforceBluetoothPrivilegedPermission(service);
3749                 service.stopSearchingForSources();
3750             } catch (RuntimeException e) {
3751                 Log.e(TAG, "Exception happened", e);
3752             }
3753         }
3754 
3755         @Override
isSearchInProgress()3756         public boolean isSearchInProgress() {
3757             try {
3758                 BassClientService service = getService();
3759                 if (service == null) {
3760                     Log.e(TAG, "Service is null");
3761                     return false;
3762                 }
3763                 enforceBluetoothPrivilegedPermission(service);
3764                 return service.isSearchInProgress();
3765             } catch (RuntimeException e) {
3766                 Log.e(TAG, "Exception happened", e);
3767                 return false;
3768             }
3769         }
3770 
3771         @Override
addSource( BluetoothDevice sink, BluetoothLeBroadcastMetadata sourceMetadata, boolean isGroupOp)3772         public void addSource(
3773                 BluetoothDevice sink,
3774                 BluetoothLeBroadcastMetadata sourceMetadata,
3775                 boolean isGroupOp) {
3776             try {
3777                 BassClientService service = getService();
3778                 if (service == null) {
3779                     Log.e(TAG, "Service is null");
3780                     return;
3781                 }
3782                 enforceBluetoothPrivilegedPermission(service);
3783                 service.addSource(sink, sourceMetadata, isGroupOp);
3784             } catch (RuntimeException e) {
3785                 Log.e(TAG, "Exception happened", e);
3786             }
3787         }
3788 
3789         @Override
modifySource( BluetoothDevice sink, int sourceId, BluetoothLeBroadcastMetadata updatedMetadata)3790         public void modifySource(
3791                 BluetoothDevice sink, int sourceId, BluetoothLeBroadcastMetadata updatedMetadata) {
3792             try {
3793                 BassClientService service = getService();
3794                 if (service == null) {
3795                     Log.e(TAG, "Service is null");
3796                     return;
3797                 }
3798                 enforceBluetoothPrivilegedPermission(service);
3799                 service.modifySource(sink, sourceId, updatedMetadata);
3800             } catch (RuntimeException e) {
3801                 Log.e(TAG, "Exception happened", e);
3802             }
3803         }
3804 
3805         @Override
removeSource(BluetoothDevice sink, int sourceId)3806         public void removeSource(BluetoothDevice sink, int sourceId) {
3807             try {
3808                 BassClientService service = getService();
3809                 if (service == null) {
3810                     Log.e(TAG, "Service is null");
3811                     return;
3812                 }
3813                 enforceBluetoothPrivilegedPermission(service);
3814                 service.removeSource(sink, sourceId);
3815             } catch (RuntimeException e) {
3816                 Log.e(TAG, "Exception happened", e);
3817             }
3818         }
3819 
3820         @Override
getAllSources(BluetoothDevice sink)3821         public List<BluetoothLeBroadcastReceiveState> getAllSources(BluetoothDevice sink) {
3822             try {
3823                 BassClientService service = getService();
3824                 if (sink == null) {
3825                     Log.e(TAG, "Service is null");
3826                     return Collections.emptyList();
3827                 }
3828                 enforceBluetoothPrivilegedPermission(service);
3829                 return service.getAllSources(sink);
3830             } catch (RuntimeException e) {
3831                 Log.e(TAG, "Exception happened", e);
3832                 return Collections.emptyList();
3833             }
3834         }
3835 
3836         @Override
getMaximumSourceCapacity(BluetoothDevice sink)3837         public int getMaximumSourceCapacity(BluetoothDevice sink) {
3838             try {
3839                 BassClientService service = getService();
3840                 if (service == null) {
3841                     Log.e(TAG, "Service is null");
3842                     return 0;
3843                 }
3844                 enforceBluetoothPrivilegedPermission(service);
3845                 return service.getMaximumSourceCapacity(sink);
3846             } catch (RuntimeException e) {
3847                 Log.e(TAG, "Exception happened", e);
3848                 return 0;
3849             }
3850         }
3851     }
3852 }
3853