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