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_FORBIDDEN;
20 
21 import android.annotation.CallbackExecutor;
22 import android.annotation.IntDef;
23 import android.bluetooth.BluetoothAdapter;
24 import android.bluetooth.BluetoothClass;
25 import android.bluetooth.BluetoothCsipSetCoordinator;
26 import android.bluetooth.BluetoothDevice;
27 import android.bluetooth.BluetoothLeAudioContentMetadata;
28 import android.bluetooth.BluetoothLeBroadcast;
29 import android.bluetooth.BluetoothLeBroadcastAssistant;
30 import android.bluetooth.BluetoothLeBroadcastMetadata;
31 import android.bluetooth.BluetoothLeBroadcastReceiveState;
32 import android.bluetooth.BluetoothLeBroadcastSettings;
33 import android.bluetooth.BluetoothLeBroadcastSubgroup;
34 import android.bluetooth.BluetoothLeBroadcastSubgroupSettings;
35 import android.bluetooth.BluetoothProfile;
36 import android.bluetooth.BluetoothProfile.ServiceListener;
37 import android.content.ContentResolver;
38 import android.content.Context;
39 import android.content.Intent;
40 import android.database.ContentObserver;
41 import android.net.Uri;
42 import android.os.Build;
43 import android.os.Handler;
44 import android.os.Looper;
45 import android.os.UserManager;
46 import android.provider.Settings;
47 import android.text.TextUtils;
48 import android.util.Log;
49 
50 import androidx.annotation.NonNull;
51 import androidx.annotation.Nullable;
52 import androidx.annotation.RequiresApi;
53 
54 import com.android.settingslib.R;
55 
56 import com.google.common.collect.ImmutableList;
57 
58 import java.lang.annotation.Retention;
59 import java.lang.annotation.RetentionPolicy;
60 import java.nio.charset.StandardCharsets;
61 import java.util.ArrayList;
62 import java.util.Arrays;
63 import java.util.Collections;
64 import java.util.List;
65 import java.util.Map;
66 import java.util.UUID;
67 import java.util.concurrent.ConcurrentHashMap;
68 import java.util.concurrent.Executor;
69 import java.util.concurrent.Executors;
70 import java.util.concurrent.ThreadLocalRandom;
71 import java.util.stream.Collectors;
72 
73 /**
74  * LocalBluetoothLeBroadcast provides an interface between the Settings app and the functionality of
75  * the local {@link BluetoothLeBroadcast}. Use the {@link BluetoothLeBroadcast.Callback} to get the
76  * result callback.
77  */
78 public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
79     public static final String ACTION_LE_AUDIO_SHARING_STATE_CHANGE =
80             "com.android.settings.action.BLUETOOTH_LE_AUDIO_SHARING_STATE_CHANGE";
81     public static final String EXTRA_LE_AUDIO_SHARING_STATE = "BLUETOOTH_LE_AUDIO_SHARING_STATE";
82     public static final String EXTRA_BLUETOOTH_DEVICE = "BLUETOOTH_DEVICE";
83     public static final int BROADCAST_STATE_UNKNOWN = 0;
84     public static final int BROADCAST_STATE_ON = 1;
85     public static final int BROADCAST_STATE_OFF = 2;
86 
87     @Retention(RetentionPolicy.SOURCE)
88     @IntDef(
89             prefix = {"BROADCAST_STATE_"},
90             value = {BROADCAST_STATE_UNKNOWN, BROADCAST_STATE_ON, BROADCAST_STATE_OFF})
91     public @interface BroadcastState {}
92 
93     private static final String SETTINGS_PKG = "com.android.settings";
94     private static final String TAG = "LocalBluetoothLeBroadcast";
95     private static final boolean DEBUG = BluetoothUtils.D;
96 
97     static final String NAME = "LE_AUDIO_BROADCAST";
98     private static final String UNDERLINE = "_";
99     private static final int DEFAULT_CODE_MAX = 9999;
100     private static final int DEFAULT_CODE_MIN = 1000;
101     // Order of this profile in device profiles list
102     private static final int ORDINAL = 1;
103     private static final int UNKNOWN_VALUE_PLACEHOLDER = -1;
104     private static final Uri[] SETTINGS_URIS =
105             new Uri[] {
106                 Settings.Secure.getUriFor(Settings.Secure.BLUETOOTH_LE_BROADCAST_NAME),
107                 Settings.Secure.getUriFor(Settings.Secure.BLUETOOTH_LE_BROADCAST_PROGRAM_INFO),
108                 Settings.Secure.getUriFor(Settings.Secure.BLUETOOTH_LE_BROADCAST_CODE),
109                 Settings.Secure.getUriFor(Settings.Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME),
110                 Settings.Secure.getUriFor(
111                         Settings.Secure.BLUETOOTH_LE_BROADCAST_IMPROVE_COMPATIBILITY),
112             };
113     private final Context mContext;
114     private final CachedBluetoothDeviceManager mDeviceManager;
115     private BluetoothLeBroadcast mServiceBroadcast;
116     private BluetoothLeBroadcastAssistant mServiceBroadcastAssistant;
117     private BluetoothLeAudioContentMetadata mBluetoothLeAudioContentMetadata;
118     private BluetoothLeBroadcastMetadata mBluetoothLeBroadcastMetadata;
119     private BluetoothLeAudioContentMetadata.Builder mBuilder;
120     private int mBroadcastId = UNKNOWN_VALUE_PLACEHOLDER;
121     private String mAppSourceName = "";
122     private String mNewAppSourceName = "";
123     private boolean mIsBroadcastProfileReady = false;
124     private boolean mIsBroadcastAssistantProfileReady = false;
125     private boolean mImproveCompatibility = false;
126     private String mProgramInfo;
127     private String mBroadcastName;
128     private byte[] mBroadcastCode;
129     private Executor mExecutor;
130     private ContentResolver mContentResolver;
131     private ContentObserver mSettingsObserver;
132     // Cached broadcast callbacks being register before service is connected.
133     private Map<BluetoothLeBroadcast.Callback, Executor> mCachedBroadcastCallbackExecutorMap =
134             new ConcurrentHashMap<>();
135 
136     private final ServiceListener mServiceListener =
137             new ServiceListener() {
138                 @Override
139                 public void onServiceConnected(int profile, BluetoothProfile proxy) {
140                     if (DEBUG) {
141                         Log.d(TAG, "Bluetooth service connected: " + profile);
142                     }
143                     if ((profile == BluetoothProfile.LE_AUDIO_BROADCAST)
144                             && !mIsBroadcastProfileReady) {
145                         mServiceBroadcast = (BluetoothLeBroadcast) proxy;
146                         mIsBroadcastProfileReady = true;
147                         registerServiceCallBack(mExecutor, mBroadcastCallback);
148                         List<BluetoothLeBroadcastMetadata> metadata = getAllBroadcastMetadata();
149                         if (!metadata.isEmpty()) {
150                             updateBroadcastInfoFromBroadcastMetadata(metadata.get(0));
151                         }
152                         registerContentObserver();
153                         if (DEBUG) {
154                             Log.d(
155                                     TAG,
156                                     "onServiceConnected: register "
157                                             + "mCachedBroadcastCallbackExecutorMap = "
158                                             + mCachedBroadcastCallbackExecutorMap);
159                         }
160                         mCachedBroadcastCallbackExecutorMap.forEach(
161                                 (callback, executor) ->
162                                         registerServiceCallBack(executor, callback));
163                     } else if ((profile == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT)
164                             && !mIsBroadcastAssistantProfileReady) {
165                         mIsBroadcastAssistantProfileReady = true;
166                         mServiceBroadcastAssistant = (BluetoothLeBroadcastAssistant) proxy;
167                         registerBroadcastAssistantCallback(mExecutor, mBroadcastAssistantCallback);
168                     }
169                 }
170 
171                 @Override
172                 public void onServiceDisconnected(int profile) {
173                     if (DEBUG) {
174                         Log.d(TAG, "Bluetooth service disconnected: " + profile);
175                     }
176                     if ((profile == BluetoothProfile.LE_AUDIO_BROADCAST)
177                             && mIsBroadcastProfileReady) {
178                         mIsBroadcastProfileReady = false;
179                         notifyBroadcastStateChange(BROADCAST_STATE_OFF);
180                         unregisterServiceCallBack(mBroadcastCallback);
181                         mCachedBroadcastCallbackExecutorMap.clear();
182                     }
183                     if ((profile == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT)
184                             && mIsBroadcastAssistantProfileReady) {
185                         mIsBroadcastAssistantProfileReady = false;
186                         unregisterBroadcastAssistantCallback(mBroadcastAssistantCallback);
187                     }
188 
189                     if (!mIsBroadcastAssistantProfileReady && !mIsBroadcastProfileReady) {
190                         unregisterContentObserver();
191                     }
192                 }
193             };
194 
195     private final BluetoothLeBroadcast.Callback mBroadcastCallback =
196             new BluetoothLeBroadcast.Callback() {
197                 @Override
198                 public void onBroadcastStarted(int reason, int broadcastId) {
199                     if (DEBUG) {
200                         Log.d(
201                                 TAG,
202                                 "onBroadcastStarted(), reason = "
203                                         + reason
204                                         + ", broadcastId = "
205                                         + broadcastId);
206                     }
207                     setLatestBroadcastId(broadcastId);
208                     setAppSourceName(mNewAppSourceName, /* updateContentResolver= */ true);
209                 }
210 
211                 @Override
212                 public void onBroadcastStartFailed(int reason) {
213                     if (DEBUG) {
214                         Log.d(TAG, "onBroadcastStartFailed(), reason = " + reason);
215                     }
216                 }
217 
218                 @Override
219                 public void onBroadcastMetadataChanged(
220                         int broadcastId, @NonNull BluetoothLeBroadcastMetadata metadata) {
221                     if (DEBUG) {
222                         Log.d(TAG, "onBroadcastMetadataChanged(), broadcastId = " + broadcastId);
223                     }
224                     setLatestBluetoothLeBroadcastMetadata(metadata);
225                     notifyBroadcastStateChange(BROADCAST_STATE_ON);
226                 }
227 
228                 @Override
229                 public void onBroadcastStopped(int reason, int broadcastId) {
230                     if (DEBUG) {
231                         Log.d(
232                                 TAG,
233                                 "onBroadcastStopped(), reason = "
234                                         + reason
235                                         + ", broadcastId = "
236                                         + broadcastId);
237                     }
238                     notifyBroadcastStateChange(BROADCAST_STATE_OFF);
239                     stopLocalSourceReceivers();
240                     resetCacheInfo();
241                 }
242 
243                 @Override
244                 public void onBroadcastStopFailed(int reason) {
245                     if (DEBUG) {
246                         Log.d(TAG, "onBroadcastStopFailed(), reason = " + reason);
247                     }
248                 }
249 
250                 @Override
251                 public void onBroadcastUpdated(int reason, int broadcastId) {
252                     if (DEBUG) {
253                         Log.d(
254                                 TAG,
255                                 "onBroadcastUpdated(), reason = "
256                                         + reason
257                                         + ", broadcastId = "
258                                         + broadcastId);
259                     }
260                     setLatestBroadcastId(broadcastId);
261                     setAppSourceName(mNewAppSourceName, /* updateContentResolver= */ true);
262                 }
263 
264                 @Override
265                 public void onBroadcastUpdateFailed(int reason, int broadcastId) {
266                     if (DEBUG) {
267                         Log.d(
268                                 TAG,
269                                 "onBroadcastUpdateFailed(), reason = "
270                                         + reason
271                                         + ", broadcastId = "
272                                         + broadcastId);
273                     }
274                 }
275 
276                 @Override
277                 public void onPlaybackStarted(int reason, int broadcastId) {}
278 
279                 @Override
280                 public void onPlaybackStopped(int reason, int broadcastId) {}
281             };
282 
283     private final BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback =
284             new BluetoothLeBroadcastAssistant.Callback() {
285                 @Override
286                 public void onSourceAdded(@NonNull BluetoothDevice sink, int sourceId, int reason) {
287                     if (DEBUG) {
288                         Log.d(
289                                 TAG,
290                                 "onSourceAdded(), sink = "
291                                         + sink
292                                         + ", reason = "
293                                         + reason
294                                         + ", sourceId = "
295                                         + sourceId);
296                     }
297                 }
298 
299                 @Override
300                 public void onSearchStarted(int reason) {}
301 
302                 @Override
303                 public void onSearchStartFailed(int reason) {}
304 
305                 @Override
306                 public void onSearchStopped(int reason) {}
307 
308                 @Override
309                 public void onSearchStopFailed(int reason) {}
310 
311                 @Override
312                 public void onSourceFound(@NonNull BluetoothLeBroadcastMetadata source) {}
313 
314                 @Override
315                 public void onSourceAddFailed(
316                         @NonNull BluetoothDevice sink,
317                         @NonNull BluetoothLeBroadcastMetadata source,
318                         int reason) {
319                     if (DEBUG) {
320                         Log.d(
321                                 TAG,
322                                 "onSourceAddFailed(), sink = "
323                                         + sink
324                                         + ", reason = "
325                                         + reason
326                                         + ", source = "
327                                         + source);
328                     }
329                 }
330 
331                 @Override
332                 public void onSourceModified(
333                         @NonNull BluetoothDevice sink, int sourceId, int reason) {}
334 
335                 @Override
336                 public void onSourceModifyFailed(
337                         @NonNull BluetoothDevice sink, int sourceId, int reason) {}
338 
339                 @Override
340                 public void onSourceRemoved(
341                         @NonNull BluetoothDevice sink, int sourceId, int reason) {
342                     if (DEBUG) {
343                         Log.d(
344                                 TAG,
345                                 "onSourceRemoved(), sink = "
346                                         + sink
347                                         + ", reason = "
348                                         + reason
349                                         + ", sourceId = "
350                                         + sourceId);
351                     }
352                 }
353 
354                 @Override
355                 public void onSourceRemoveFailed(
356                         @NonNull BluetoothDevice sink, int sourceId, int reason) {
357                     if (DEBUG) {
358                         Log.d(
359                                 TAG,
360                                 "onSourceRemoveFailed(), sink = "
361                                         + sink
362                                         + ", reason = "
363                                         + reason
364                                         + ", sourceId = "
365                                         + sourceId);
366                     }
367                 }
368 
369                 @Override
370                 public void onReceiveStateChanged(
371                         @NonNull BluetoothDevice sink,
372                         int sourceId,
373                         @NonNull BluetoothLeBroadcastReceiveState state) {
374                     if (DEBUG) {
375                         Log.d(
376                                 TAG,
377                                 "onReceiveStateChanged(), sink = "
378                                         + sink
379                                         + ", sourceId = "
380                                         + sourceId
381                                         + ", state = "
382                                         + state);
383                     }
384                     if (BluetoothUtils.isConnected(state)) {
385                         updateFallbackActiveDeviceIfNeeded();
386                     }
387                 }
388             };
389 
390     private class BroadcastSettingsObserver extends ContentObserver {
BroadcastSettingsObserver(Handler h)391         BroadcastSettingsObserver(Handler h) {
392             super(h);
393         }
394 
395         @Override
onChange(boolean selfChange)396         public void onChange(boolean selfChange) {
397             Log.d(TAG, "BroadcastSettingsObserver: onChange");
398             updateBroadcastInfoFromContentProvider();
399         }
400     }
401 
LocalBluetoothLeBroadcast(Context context, CachedBluetoothDeviceManager deviceManager)402     LocalBluetoothLeBroadcast(Context context, CachedBluetoothDeviceManager deviceManager) {
403         mContext = context;
404         mDeviceManager = deviceManager;
405         mExecutor = Executors.newSingleThreadExecutor();
406         mBuilder = new BluetoothLeAudioContentMetadata.Builder();
407         mContentResolver = context.getContentResolver();
408         Handler handler = new Handler(Looper.getMainLooper());
409         mSettingsObserver = new BroadcastSettingsObserver(handler);
410         updateBroadcastInfoFromContentProvider();
411 
412         // Before registering callback, the constructor should finish creating the all of variables.
413         BluetoothAdapter.getDefaultAdapter()
414                 .getProfileProxy(context, mServiceListener, BluetoothProfile.LE_AUDIO_BROADCAST);
415         BluetoothAdapter.getDefaultAdapter()
416                 .getProfileProxy(
417                         context, mServiceListener, BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
418     }
419 
420     /**
421      * Start the LE Broadcast. If the system started the LE Broadcast, then the system calls the
422      * corresponding callback {@link BluetoothLeBroadcast.Callback}.
423      */
startBroadcast(String appSourceName, String language)424     public void startBroadcast(String appSourceName, String language) {
425         mNewAppSourceName = appSourceName;
426         if (mServiceBroadcast == null) {
427             Log.d(TAG, "The BluetoothLeBroadcast is null when starting the broadcast.");
428             return;
429         }
430         String programInfo = getProgramInfo();
431         if (DEBUG) {
432             Log.d(TAG, "startBroadcast: language = " + language + " ,programInfo = " + programInfo);
433         }
434         buildContentMetadata(language, programInfo);
435         mServiceBroadcast.startBroadcast(
436                 mBluetoothLeAudioContentMetadata,
437                 (mBroadcastCode != null && mBroadcastCode.length > 0) ? mBroadcastCode : null);
438     }
439 
440     /**
441      * Start the private Broadcast for personal audio sharing or qr code sharing.
442      *
443      * <p>The broadcast will use random string for both broadcast name and subgroup program info;
444      * The broadcast will use random string for broadcast code; The broadcast will only have one
445      * subgroup due to system limitation; The subgroup language will be null.
446      *
447      * <p>If the system started the LE Broadcast, then the system calls the corresponding callback
448      * {@link BluetoothLeBroadcast.Callback}.
449      */
startPrivateBroadcast()450     public void startPrivateBroadcast() {
451         mNewAppSourceName = "Sharing audio";
452         if (mServiceBroadcast == null) {
453             Log.d(TAG, "The BluetoothLeBroadcast is null when starting the private broadcast.");
454             return;
455         }
456         if (mServiceBroadcast.getAllBroadcastMetadata().size()
457                 >= mServiceBroadcast.getMaximumNumberOfBroadcasts()) {
458             Log.d(TAG, "Skip starting the broadcast due to number limit.");
459             return;
460         }
461         String broadcastName = getBroadcastName();
462         String programInfo = getProgramInfo();
463         boolean improveCompatibility = getImproveCompatibility();
464         if (DEBUG) {
465             Log.d(
466                     TAG,
467                     "startBroadcast: language = null , programInfo = "
468                             + programInfo
469                             + ", broadcastName = "
470                             + broadcastName
471                             + ", improveCompatibility = "
472                             + improveCompatibility);
473         }
474         // Current broadcast framework only support one subgroup
475         BluetoothLeBroadcastSubgroupSettings subgroupSettings =
476                 buildBroadcastSubgroupSettings(
477                         /* language= */ null, programInfo, improveCompatibility);
478         BluetoothLeBroadcastSettings settings =
479                 buildBroadcastSettings(
480                         true, // TODO: set to false after framework fix
481                         TextUtils.isEmpty(broadcastName) ? null : broadcastName,
482                         (mBroadcastCode != null && mBroadcastCode.length > 0)
483                                 ? mBroadcastCode
484                                 : null,
485                         ImmutableList.of(subgroupSettings));
486         mServiceBroadcast.startBroadcast(settings);
487     }
488 
489     /** Checks if the broadcast is playing. */
isPlaying(int broadcastId)490     public boolean isPlaying(int broadcastId) {
491         if (mServiceBroadcast == null) {
492             Log.d(TAG, "check isPlaying failed, the BluetoothLeBroadcast is null.");
493             return false;
494         }
495         return mServiceBroadcast.isPlaying(broadcastId);
496     }
497 
buildBroadcastSettings( boolean isPublic, @Nullable String broadcastName, @Nullable byte[] broadcastCode, List<BluetoothLeBroadcastSubgroupSettings> subgroupSettingsList)498     private BluetoothLeBroadcastSettings buildBroadcastSettings(
499             boolean isPublic,
500             @Nullable String broadcastName,
501             @Nullable byte[] broadcastCode,
502             List<BluetoothLeBroadcastSubgroupSettings> subgroupSettingsList) {
503         BluetoothLeBroadcastSettings.Builder builder =
504                 new BluetoothLeBroadcastSettings.Builder()
505                         .setPublicBroadcast(isPublic)
506                         .setBroadcastName(broadcastName)
507                         .setBroadcastCode(broadcastCode);
508         for (BluetoothLeBroadcastSubgroupSettings subgroupSettings : subgroupSettingsList) {
509             builder.addSubgroupSettings(subgroupSettings);
510         }
511         return builder.build();
512     }
513 
buildBroadcastSubgroupSettings( @ullable String language, @Nullable String programInfo, boolean improveCompatibility)514     private BluetoothLeBroadcastSubgroupSettings buildBroadcastSubgroupSettings(
515             @Nullable String language, @Nullable String programInfo, boolean improveCompatibility) {
516         BluetoothLeAudioContentMetadata metadata =
517                 new BluetoothLeAudioContentMetadata.Builder()
518                         .setLanguage(language)
519                         .setProgramInfo(programInfo)
520                         .build();
521         // Current broadcast framework only support one subgroup, thus we still maintain the latest
522         // metadata to keep legacy UI working.
523         mBluetoothLeAudioContentMetadata = metadata;
524         return new BluetoothLeBroadcastSubgroupSettings.Builder()
525                 .setPreferredQuality(
526                         improveCompatibility
527                                 ? BluetoothLeBroadcastSubgroupSettings.QUALITY_STANDARD
528                                 : BluetoothLeBroadcastSubgroupSettings.QUALITY_HIGH)
529                 .setContentMetadata(mBluetoothLeAudioContentMetadata)
530                 .build();
531     }
532 
getProgramInfo()533     public String getProgramInfo() {
534         return mProgramInfo;
535     }
536 
setProgramInfo(String programInfo)537     public void setProgramInfo(String programInfo) {
538         setProgramInfo(programInfo, /* updateContentResolver= */ true);
539     }
540 
setProgramInfo(String programInfo, boolean updateContentResolver)541     private void setProgramInfo(String programInfo, boolean updateContentResolver) {
542         if (TextUtils.isEmpty(programInfo)) {
543             Log.d(TAG, "setProgramInfo: programInfo is null or empty");
544             return;
545         }
546         if (mProgramInfo != null && TextUtils.equals(mProgramInfo, programInfo)) {
547             Log.d(TAG, "setProgramInfo: programInfo is not changed");
548             return;
549         }
550         Log.d(TAG, "setProgramInfo: " + programInfo);
551         mProgramInfo = programInfo;
552         if (updateContentResolver) {
553             if (mContentResolver == null) {
554                 Log.d(TAG, "mContentResolver is null");
555                 return;
556             }
557             Settings.Secure.putString(
558                     mContentResolver,
559                     Settings.Secure.BLUETOOTH_LE_BROADCAST_PROGRAM_INFO,
560                     programInfo);
561         }
562     }
563 
getBroadcastName()564     public String getBroadcastName() {
565         return mBroadcastName;
566     }
567 
568     /** Set broadcast name. */
setBroadcastName(String broadcastName)569     public void setBroadcastName(String broadcastName) {
570         setBroadcastName(broadcastName, /* updateContentResolver= */ true);
571     }
572 
setBroadcastName(String broadcastName, boolean updateContentResolver)573     private void setBroadcastName(String broadcastName, boolean updateContentResolver) {
574         if (TextUtils.isEmpty(broadcastName)) {
575             Log.d(TAG, "setBroadcastName: broadcastName is null or empty");
576             return;
577         }
578         if (mBroadcastName != null && TextUtils.equals(mBroadcastName, broadcastName)) {
579             Log.d(TAG, "setBroadcastName: broadcastName is not changed");
580             return;
581         }
582         Log.d(TAG, "setBroadcastName: " + broadcastName);
583         mBroadcastName = broadcastName;
584         if (updateContentResolver) {
585             if (mContentResolver == null) {
586                 Log.d(TAG, "mContentResolver is null");
587                 return;
588             }
589             Settings.Secure.putString(
590                     mContentResolver, Settings.Secure.BLUETOOTH_LE_BROADCAST_NAME, broadcastName);
591         }
592     }
593 
getBroadcastCode()594     public byte[] getBroadcastCode() {
595         return mBroadcastCode;
596     }
597 
setBroadcastCode(byte[] broadcastCode)598     public void setBroadcastCode(byte[] broadcastCode) {
599         setBroadcastCode(broadcastCode, /* updateContentResolver= */ true);
600     }
601 
setBroadcastCode(byte[] broadcastCode, boolean updateContentResolver)602     private void setBroadcastCode(byte[] broadcastCode, boolean updateContentResolver) {
603         if (broadcastCode == null) {
604             Log.d(TAG, "setBroadcastCode: broadcastCode is null");
605             return;
606         }
607         if (mBroadcastCode != null && Arrays.equals(broadcastCode, mBroadcastCode)) {
608             Log.d(TAG, "setBroadcastCode: broadcastCode is not changed");
609             return;
610         }
611         mBroadcastCode = broadcastCode;
612         if (updateContentResolver) {
613             if (mContentResolver == null) {
614                 Log.d(TAG, "mContentResolver is null");
615                 return;
616             }
617             Settings.Secure.putString(
618                     mContentResolver,
619                     Settings.Secure.BLUETOOTH_LE_BROADCAST_CODE,
620                     new String(broadcastCode, StandardCharsets.UTF_8));
621         }
622     }
623 
624     /** Get compatibility config for broadcast. */
getImproveCompatibility()625     public boolean getImproveCompatibility() {
626         return mImproveCompatibility;
627     }
628 
629     /** Set compatibility config for broadcast. */
setImproveCompatibility(boolean improveCompatibility)630     public void setImproveCompatibility(boolean improveCompatibility) {
631         setImproveCompatibility(improveCompatibility, /* updateContentResolver= */ true);
632     }
633 
setImproveCompatibility( boolean improveCompatibility, boolean updateContentResolver)634     private void setImproveCompatibility(
635             boolean improveCompatibility, boolean updateContentResolver) {
636         if (mImproveCompatibility == improveCompatibility) {
637             Log.d(TAG, "setImproveCompatibility: improveCompatibility is not changed");
638             return;
639         }
640         mImproveCompatibility = improveCompatibility;
641         if (updateContentResolver) {
642             if (mContentResolver == null) {
643                 Log.d(TAG, "mContentResolver is null");
644                 return;
645             }
646             Log.d(TAG, "Set improveCompatibility to: " + improveCompatibility);
647             Settings.Secure.putString(
648                     mContentResolver,
649                     Settings.Secure.BLUETOOTH_LE_BROADCAST_IMPROVE_COMPATIBILITY,
650                     improveCompatibility ? "1" : "0");
651         }
652     }
653 
setLatestBroadcastId(int broadcastId)654     private void setLatestBroadcastId(int broadcastId) {
655         Log.d(TAG, "setLatestBroadcastId: mBroadcastId is " + broadcastId);
656         mBroadcastId = broadcastId;
657     }
658 
getLatestBroadcastId()659     public int getLatestBroadcastId() {
660         return mBroadcastId;
661     }
662 
setAppSourceName(String appSourceName, boolean updateContentResolver)663     private void setAppSourceName(String appSourceName, boolean updateContentResolver) {
664         if (TextUtils.isEmpty(appSourceName)) {
665             appSourceName = "";
666         }
667         if (mAppSourceName != null && TextUtils.equals(mAppSourceName, appSourceName)) {
668             Log.d(TAG, "setAppSourceName: appSourceName is not changed");
669             return;
670         }
671         mAppSourceName = appSourceName;
672         mNewAppSourceName = "";
673         if (updateContentResolver) {
674             if (mContentResolver == null) {
675                 Log.d(TAG, "mContentResolver is null");
676                 return;
677             }
678             Settings.Secure.putString(
679                     mContentResolver,
680                     Settings.Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME,
681                     mAppSourceName);
682         }
683     }
684 
getAppSourceName()685     public String getAppSourceName() {
686         return mAppSourceName;
687     }
688 
setLatestBluetoothLeBroadcastMetadata( BluetoothLeBroadcastMetadata bluetoothLeBroadcastMetadata)689     private void setLatestBluetoothLeBroadcastMetadata(
690             BluetoothLeBroadcastMetadata bluetoothLeBroadcastMetadata) {
691         if (bluetoothLeBroadcastMetadata != null
692                 && bluetoothLeBroadcastMetadata.getBroadcastId() == mBroadcastId) {
693             mBluetoothLeBroadcastMetadata = bluetoothLeBroadcastMetadata;
694             updateBroadcastInfoFromBroadcastMetadata(bluetoothLeBroadcastMetadata);
695         }
696     }
697 
getLatestBluetoothLeBroadcastMetadata()698     public BluetoothLeBroadcastMetadata getLatestBluetoothLeBroadcastMetadata() {
699         if (mServiceBroadcast == null) {
700             Log.d(TAG, "The BluetoothLeBroadcast is null");
701             return null;
702         }
703         if (mBluetoothLeBroadcastMetadata == null) {
704             final List<BluetoothLeBroadcastMetadata> metadataList =
705                     mServiceBroadcast.getAllBroadcastMetadata();
706             mBluetoothLeBroadcastMetadata =
707                     metadataList.stream()
708                             .filter(i -> i.getBroadcastId() == mBroadcastId)
709                             .findFirst()
710                             .orElse(null);
711         }
712         return mBluetoothLeBroadcastMetadata;
713     }
714 
updateBroadcastInfoFromContentProvider()715     private void updateBroadcastInfoFromContentProvider() {
716         if (mContentResolver == null) {
717             Log.d(TAG, "updateBroadcastInfoFromContentProvider: mContentResolver is null");
718             return;
719         }
720         String programInfo =
721                 Settings.Secure.getString(
722                         mContentResolver, Settings.Secure.BLUETOOTH_LE_BROADCAST_PROGRAM_INFO);
723         if (programInfo == null) {
724             programInfo = getDefaultValueOfProgramInfo();
725         }
726         setProgramInfo(programInfo, /* updateContentResolver= */ false);
727 
728         String broadcastName =
729                 Settings.Secure.getString(
730                         mContentResolver, Settings.Secure.BLUETOOTH_LE_BROADCAST_NAME);
731         if (broadcastName == null) {
732             broadcastName = getDefaultValueOfBroadcastName();
733         }
734         setBroadcastName(broadcastName, /* updateContentResolver= */ false);
735 
736         String prefBroadcastCode =
737                 Settings.Secure.getString(
738                         mContentResolver, Settings.Secure.BLUETOOTH_LE_BROADCAST_CODE);
739         byte[] broadcastCode =
740                 (prefBroadcastCode == null)
741                         ? getDefaultValueOfBroadcastCode()
742                         : prefBroadcastCode.getBytes(StandardCharsets.UTF_8);
743         setBroadcastCode(broadcastCode, /* updateContentResolver= */ false);
744 
745         String appSourceName =
746                 Settings.Secure.getString(
747                         mContentResolver, Settings.Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME);
748         setAppSourceName(appSourceName, /* updateContentResolver= */ false);
749 
750         String improveCompatibility =
751                 Settings.Secure.getString(
752                         mContentResolver,
753                         Settings.Secure.BLUETOOTH_LE_BROADCAST_IMPROVE_COMPATIBILITY);
754         setImproveCompatibility(
755                 improveCompatibility == null ? false : improveCompatibility.equals("1"),
756                 /* updateContentResolver= */ false);
757     }
758 
updateBroadcastInfoFromBroadcastMetadata( BluetoothLeBroadcastMetadata bluetoothLeBroadcastMetadata)759     private void updateBroadcastInfoFromBroadcastMetadata(
760             BluetoothLeBroadcastMetadata bluetoothLeBroadcastMetadata) {
761         if (bluetoothLeBroadcastMetadata == null) {
762             Log.d(TAG, "The bluetoothLeBroadcastMetadata is null");
763             return;
764         }
765         setBroadcastName(bluetoothLeBroadcastMetadata.getBroadcastName());
766         setBroadcastCode(bluetoothLeBroadcastMetadata.getBroadcastCode());
767         setLatestBroadcastId(bluetoothLeBroadcastMetadata.getBroadcastId());
768 
769         List<BluetoothLeBroadcastSubgroup> subgroup = bluetoothLeBroadcastMetadata.getSubgroups();
770         if (subgroup == null || subgroup.size() < 1) {
771             Log.d(TAG, "The subgroup is not valid value");
772             return;
773         }
774         BluetoothLeAudioContentMetadata contentMetadata = subgroup.get(0).getContentMetadata();
775         setProgramInfo(contentMetadata.getProgramInfo());
776         setAppSourceName(getAppSourceName(), /* updateContentResolver= */ true);
777     }
778 
779     /**
780      * Stop the latest LE Broadcast. If the system stopped the LE Broadcast, then the system calls
781      * the corresponding callback {@link BluetoothLeBroadcast.Callback}.
782      */
stopLatestBroadcast()783     public void stopLatestBroadcast() {
784         stopBroadcast(mBroadcastId);
785     }
786 
787     /**
788      * Stop the LE Broadcast. If the system stopped the LE Broadcast, then the system calls the
789      * corresponding callback {@link BluetoothLeBroadcast.Callback}.
790      */
stopBroadcast(int broadcastId)791     public void stopBroadcast(int broadcastId) {
792         if (mServiceBroadcast == null) {
793             Log.d(TAG, "The BluetoothLeBroadcast is null when stopping the broadcast.");
794             return;
795         }
796         if (DEBUG) {
797             Log.d(TAG, "stopBroadcast()");
798         }
799         mServiceBroadcast.stopBroadcast(broadcastId);
800     }
801 
802     /**
803      * Update the LE Broadcast. If the system stopped the LE Broadcast, then the system calls the
804      * corresponding callback {@link BluetoothLeBroadcast.Callback}.
805      */
updateBroadcast(String appSourceName, String language)806     public void updateBroadcast(String appSourceName, String language) {
807         if (mServiceBroadcast == null) {
808             Log.d(TAG, "The BluetoothLeBroadcast is null when updating the broadcast.");
809             return;
810         }
811         String programInfo = getProgramInfo();
812         if (DEBUG) {
813             Log.d(
814                     TAG,
815                     "updateBroadcast: language = " + language + " ,programInfo = " + programInfo);
816         }
817         mNewAppSourceName = appSourceName;
818         mBluetoothLeAudioContentMetadata = mBuilder.setProgramInfo(programInfo).build();
819         mServiceBroadcast.updateBroadcast(mBroadcastId, mBluetoothLeAudioContentMetadata);
820     }
821 
822     /**
823      * Update the LE Broadcast by calling {@link BluetoothLeBroadcast#updateBroadcast(int,
824      * BluetoothLeBroadcastSettings)}, currently only updates broadcast name and program info.
825      */
updateBroadcast()826     public void updateBroadcast() {
827         if (mServiceBroadcast == null) {
828             Log.d(TAG, "The BluetoothLeBroadcast is null when updating the broadcast.");
829             return;
830         }
831         String programInfo = getProgramInfo();
832         String broadcastName = getBroadcastName();
833         mBluetoothLeAudioContentMetadata = mBuilder.setProgramInfo(programInfo).build();
834         // LeAudioService#updateBroadcast doesn't update broadcastCode, isPublicBroadcast and
835         // preferredQuality, so we leave them unset here.
836         // TODO: maybe setPublicBroadcastMetadata
837         BluetoothLeBroadcastSettings settings =
838                 new BluetoothLeBroadcastSettings.Builder()
839                         .setBroadcastName(broadcastName)
840                         .addSubgroupSettings(
841                                 new BluetoothLeBroadcastSubgroupSettings.Builder()
842                                         .setContentMetadata(mBluetoothLeAudioContentMetadata)
843                                         .build())
844                         .build();
845         if (DEBUG) {
846             Log.d(
847                     TAG,
848                     "updateBroadcast: broadcastName = "
849                             + broadcastName
850                             + " programInfo = "
851                             + programInfo);
852         }
853         mServiceBroadcast.updateBroadcast(mBroadcastId, settings);
854     }
855 
856     /**
857      * Register Broadcast Callbacks to track its state and receivers
858      *
859      * @param executor Executor object for callback
860      * @param callback Callback object to be registered
861      */
registerServiceCallBack( @onNull @allbackExecutor Executor executor, @NonNull BluetoothLeBroadcast.Callback callback)862     public void registerServiceCallBack(
863             @NonNull @CallbackExecutor Executor executor,
864             @NonNull BluetoothLeBroadcast.Callback callback) {
865         if (mServiceBroadcast == null) {
866             Log.d(TAG, "registerServiceCallBack failed, the BluetoothLeBroadcast is null.");
867             mCachedBroadcastCallbackExecutorMap.putIfAbsent(callback, executor);
868             return;
869         }
870 
871         try {
872             mServiceBroadcast.registerCallback(executor, callback);
873         } catch (IllegalArgumentException e) {
874             Log.w(TAG, "registerServiceCallBack failed. " + e.getMessage());
875         }
876     }
877 
878     /**
879      * Register Broadcast Assistant Callbacks to track its state and receivers
880      *
881      * @param executor Executor object for callback
882      * @param callback Callback object to be registered
883      */
registerBroadcastAssistantCallback( @onNull @allbackExecutor Executor executor, @NonNull BluetoothLeBroadcastAssistant.Callback callback)884     private void registerBroadcastAssistantCallback(
885             @NonNull @CallbackExecutor Executor executor,
886             @NonNull BluetoothLeBroadcastAssistant.Callback callback) {
887         if (mServiceBroadcastAssistant == null) {
888             Log.d(
889                     TAG,
890                     "registerBroadcastAssistantCallback failed, "
891                             + "the BluetoothLeBroadcastAssistant is null.");
892             return;
893         }
894 
895         mServiceBroadcastAssistant.registerCallback(executor, callback);
896     }
897 
898     /**
899      * Unregister previously registered Broadcast Callbacks
900      *
901      * @param callback Callback object to be unregistered
902      */
unregisterServiceCallBack(@onNull BluetoothLeBroadcast.Callback callback)903     public void unregisterServiceCallBack(@NonNull BluetoothLeBroadcast.Callback callback) {
904         mCachedBroadcastCallbackExecutorMap.remove(callback);
905         if (mServiceBroadcast == null) {
906             Log.d(TAG, "unregisterServiceCallBack failed, the BluetoothLeBroadcast is null.");
907             return;
908         }
909 
910         try {
911             mServiceBroadcast.unregisterCallback(callback);
912         } catch (IllegalArgumentException e) {
913             Log.w(TAG, "unregisterServiceCallBack failed. " + e.getMessage());
914         }
915     }
916 
917     /**
918      * Unregister previously registered Broadcast Assistant Callbacks
919      *
920      * @param callback Callback object to be unregistered
921      */
unregisterBroadcastAssistantCallback( @onNull BluetoothLeBroadcastAssistant.Callback callback)922     private void unregisterBroadcastAssistantCallback(
923             @NonNull BluetoothLeBroadcastAssistant.Callback callback) {
924         if (mServiceBroadcastAssistant == null) {
925             Log.d(
926                     TAG,
927                     "unregisterBroadcastAssistantCallback, "
928                             + "the BluetoothLeBroadcastAssistant is null.");
929             return;
930         }
931 
932         mServiceBroadcastAssistant.unregisterCallback(callback);
933     }
934 
buildContentMetadata(String language, String programInfo)935     private void buildContentMetadata(String language, String programInfo) {
936         mBluetoothLeAudioContentMetadata =
937                 mBuilder.setLanguage(language).setProgramInfo(programInfo).build();
938     }
939 
getLocalBluetoothLeBroadcastMetaData()940     public LocalBluetoothLeBroadcastMetadata getLocalBluetoothLeBroadcastMetaData() {
941         final BluetoothLeBroadcastMetadata metadata = getLatestBluetoothLeBroadcastMetadata();
942         if (metadata == null) {
943             Log.d(TAG, "The BluetoothLeBroadcastMetadata is null.");
944             return null;
945         }
946         return new LocalBluetoothLeBroadcastMetadata(metadata);
947     }
948 
isProfileReady()949     public boolean isProfileReady() {
950         return mIsBroadcastProfileReady;
951     }
952 
953     @Override
getProfileId()954     public int getProfileId() {
955         return BluetoothProfile.LE_AUDIO_BROADCAST;
956     }
957 
accessProfileEnabled()958     public boolean accessProfileEnabled() {
959         return false;
960     }
961 
isAutoConnectable()962     public boolean isAutoConnectable() {
963         return true;
964     }
965 
966     /** Not supported since LE Audio Broadcasts do not establish a connection. */
getConnectionStatus(BluetoothDevice device)967     public int getConnectionStatus(BluetoothDevice device) {
968         if (mServiceBroadcast == null) {
969             return BluetoothProfile.STATE_DISCONNECTED;
970         }
971         // LE Audio Broadcasts are not connection-oriented.
972         return mServiceBroadcast.getConnectionState(device);
973     }
974 
975     /** Not supported since LE Audio Broadcasts do not establish a connection. */
getConnectedDevices()976     public List<BluetoothDevice> getConnectedDevices() {
977         if (mServiceBroadcast == null) {
978             return new ArrayList<BluetoothDevice>(0);
979         }
980         // LE Audio Broadcasts are not connection-oriented.
981         return mServiceBroadcast.getConnectedDevices();
982     }
983 
984     /** Get all broadcast metadata. */
getAllBroadcastMetadata()985     public @NonNull List<BluetoothLeBroadcastMetadata> getAllBroadcastMetadata() {
986         if (mServiceBroadcast == null) {
987             Log.d(TAG, "The BluetoothLeBroadcast is null.");
988             return Collections.emptyList();
989         }
990 
991         return mServiceBroadcast.getAllBroadcastMetadata();
992     }
993 
isEnabled(BluetoothDevice device)994     public boolean isEnabled(BluetoothDevice device) {
995         if (mServiceBroadcast == null) {
996             return false;
997         }
998 
999         return !mServiceBroadcast.getAllBroadcastMetadata().isEmpty();
1000     }
1001 
1002     /** Service does not provide method to get/set policy. */
getConnectionPolicy(BluetoothDevice device)1003     public int getConnectionPolicy(BluetoothDevice device) {
1004         return CONNECTION_POLICY_FORBIDDEN;
1005     }
1006 
1007     /**
1008      * Service does not provide "setEnabled" method. Please use {@link #startBroadcast}, {@link
1009      * #stopBroadcast()} or {@link #updateBroadcast(String, String)}
1010      */
setEnabled(BluetoothDevice device, boolean enabled)1011     public boolean setEnabled(BluetoothDevice device, boolean enabled) {
1012         return false;
1013     }
1014 
toString()1015     public String toString() {
1016         return NAME;
1017     }
1018 
getOrdinal()1019     public int getOrdinal() {
1020         return ORDINAL;
1021     }
1022 
getNameResource(BluetoothDevice device)1023     public int getNameResource(BluetoothDevice device) {
1024         return R.string.summary_empty;
1025     }
1026 
getSummaryResourceForDevice(BluetoothDevice device)1027     public int getSummaryResourceForDevice(BluetoothDevice device) {
1028         int state = getConnectionStatus(device);
1029         return BluetoothUtils.getConnectionStateSummary(state);
1030     }
1031 
getDrawableResource(BluetoothClass btClass)1032     public int getDrawableResource(BluetoothClass btClass) {
1033         return 0;
1034     }
1035 
1036     @RequiresApi(Build.VERSION_CODES.S)
finalize()1037     protected void finalize() {
1038         if (DEBUG) {
1039             Log.d(TAG, "finalize()");
1040         }
1041         if (mServiceBroadcast != null) {
1042             try {
1043                 BluetoothAdapter.getDefaultAdapter()
1044                         .closeProfileProxy(BluetoothProfile.LE_AUDIO_BROADCAST, mServiceBroadcast);
1045                 mServiceBroadcast = null;
1046             } catch (Throwable t) {
1047                 Log.w(TAG, "Error cleaning up LeAudio proxy", t);
1048             }
1049         }
1050     }
1051 
getDefaultValueOfBroadcastName()1052     private String getDefaultValueOfBroadcastName() {
1053         // set the default value;
1054         int postfix = ThreadLocalRandom.current().nextInt(DEFAULT_CODE_MIN, DEFAULT_CODE_MAX);
1055         return BluetoothAdapter.getDefaultAdapter().getName() + UNDERLINE + postfix;
1056     }
1057 
getDefaultValueOfProgramInfo()1058     private String getDefaultValueOfProgramInfo() {
1059         // set the default value;
1060         int postfix = ThreadLocalRandom.current().nextInt(DEFAULT_CODE_MIN, DEFAULT_CODE_MAX);
1061         return BluetoothAdapter.getDefaultAdapter().getName() + UNDERLINE + postfix;
1062     }
1063 
getDefaultValueOfBroadcastCode()1064     private byte[] getDefaultValueOfBroadcastCode() {
1065         // set the default value;
1066         return generateRandomPassword().getBytes(StandardCharsets.UTF_8);
1067     }
1068 
resetCacheInfo()1069     private void resetCacheInfo() {
1070         if (DEBUG) {
1071             Log.d(TAG, "resetCacheInfo:");
1072         }
1073         setAppSourceName("", /* updateContentResolver= */ true);
1074         mBluetoothLeBroadcastMetadata = null;
1075         mBroadcastId = UNKNOWN_VALUE_PLACEHOLDER;
1076     }
1077 
generateRandomPassword()1078     private String generateRandomPassword() {
1079         String randomUUID = UUID.randomUUID().toString();
1080         // first 12 chars from xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
1081         return randomUUID.substring(0, 8) + randomUUID.substring(9, 13);
1082     }
1083 
registerContentObserver()1084     private void registerContentObserver() {
1085         if (mContentResolver == null) {
1086             Log.d(TAG, "mContentResolver is null");
1087             return;
1088         }
1089         for (Uri uri : SETTINGS_URIS) {
1090             mContentResolver.registerContentObserver(uri, false, mSettingsObserver);
1091         }
1092     }
1093 
unregisterContentObserver()1094     private void unregisterContentObserver() {
1095         if (mContentResolver == null) {
1096             Log.d(TAG, "mContentResolver is null");
1097             return;
1098         }
1099         mContentResolver.unregisterContentObserver(mSettingsObserver);
1100     }
1101 
stopLocalSourceReceivers()1102     private void stopLocalSourceReceivers() {
1103         if (DEBUG) {
1104             Log.d(TAG, "stopLocalSourceReceivers()");
1105         }
1106         for (BluetoothDevice device : mServiceBroadcastAssistant.getConnectedDevices()) {
1107             for (BluetoothLeBroadcastReceiveState receiveState :
1108                     mServiceBroadcastAssistant.getAllSources(device)) {
1109                 /* Check if local/last broadcast is the synced one */
1110                 int localBroadcastId = getLatestBroadcastId();
1111                 if (receiveState.getBroadcastId() != localBroadcastId) continue;
1112 
1113                 mServiceBroadcastAssistant.removeSource(device, receiveState.getSourceId());
1114             }
1115         }
1116     }
1117 
1118     /** Update fallback active device if needed. */
updateFallbackActiveDeviceIfNeeded()1119     public void updateFallbackActiveDeviceIfNeeded() {
1120         if (mServiceBroadcast == null) {
1121             Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded due to broadcast profile is null");
1122             return;
1123         }
1124         List<BluetoothLeBroadcastMetadata> sources = mServiceBroadcast.getAllBroadcastMetadata();
1125         if (sources.stream()
1126                 .noneMatch(source -> mServiceBroadcast.isPlaying(source.getBroadcastId()))) {
1127             Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded due to no broadcast ongoing");
1128             return;
1129         }
1130         if (mServiceBroadcastAssistant == null) {
1131             Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded due to assistant profile is null");
1132             return;
1133         }
1134         List<BluetoothDevice> connectedDevices = mServiceBroadcastAssistant.getConnectedDevices();
1135         List<BluetoothDevice> devicesInSharing =
1136                 connectedDevices.stream()
1137                         .filter(
1138                                 bluetoothDevice -> {
1139                                     List<BluetoothLeBroadcastReceiveState> sourceList =
1140                                             mServiceBroadcastAssistant.getAllSources(
1141                                                     bluetoothDevice);
1142                                     return !sourceList.isEmpty()
1143                                             && sourceList.stream()
1144                                                     .anyMatch(BluetoothUtils::isConnected);
1145                                 })
1146                         .collect(Collectors.toList());
1147         if (devicesInSharing.isEmpty()) {
1148             Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded due to no sinks in broadcast");
1149             return;
1150         }
1151         List<BluetoothDevice> devices =
1152                 BluetoothAdapter.getDefaultAdapter().getMostRecentlyConnectedDevices();
1153         BluetoothDevice targetDevice = null;
1154         // Find the earliest connected device in sharing session.
1155         int targetDeviceIdx = -1;
1156         for (BluetoothDevice device : devicesInSharing) {
1157             if (devices.contains(device)) {
1158                 int idx = devices.indexOf(device);
1159                 if (idx > targetDeviceIdx) {
1160                     targetDeviceIdx = idx;
1161                     targetDevice = device;
1162                 }
1163             }
1164         }
1165         if (targetDevice == null) {
1166             Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded, target is null");
1167             return;
1168         }
1169         Log.d(
1170                 TAG,
1171                 "updateFallbackActiveDeviceIfNeeded, set active device: "
1172                         + targetDevice.getAnonymizedAddress());
1173         CachedBluetoothDevice targetCachedDevice = mDeviceManager.findDevice(targetDevice);
1174         if (targetCachedDevice == null) {
1175             Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded, fail to find cached bt device");
1176             return;
1177         }
1178         int fallbackActiveGroupId = getFallbackActiveGroupId();
1179         if (fallbackActiveGroupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID
1180                 && getGroupId(targetCachedDevice) == fallbackActiveGroupId) {
1181             Log.d(
1182                     TAG,
1183                     "Skip updateFallbackActiveDeviceIfNeeded, already is fallback: "
1184                             + fallbackActiveGroupId);
1185             return;
1186         }
1187         targetCachedDevice.setActive();
1188     }
1189 
getFallbackActiveGroupId()1190     private int getFallbackActiveGroupId() {
1191         return Settings.Secure.getInt(
1192                 mContext.getContentResolver(),
1193                 "bluetooth_le_broadcast_fallback_active_group_id",
1194                 BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
1195     }
1196 
getGroupId(CachedBluetoothDevice cachedDevice)1197     private int getGroupId(CachedBluetoothDevice cachedDevice) {
1198         int groupId = cachedDevice.getGroupId();
1199         String anonymizedAddress = cachedDevice.getDevice().getAnonymizedAddress();
1200         if (groupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
1201             Log.d(TAG, "getGroupId by CSIP profile for device: " + anonymizedAddress);
1202             return groupId;
1203         }
1204         for (LocalBluetoothProfile profile : cachedDevice.getProfiles()) {
1205             if (profile instanceof LeAudioProfile) {
1206                 Log.d(TAG, "getGroupId by LEA profile for device: " + anonymizedAddress);
1207                 return ((LeAudioProfile) profile).getGroupId(cachedDevice.getDevice());
1208             }
1209         }
1210         Log.d(TAG, "getGroupId return invalid id for device: " + anonymizedAddress);
1211         return BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
1212     }
1213 
notifyBroadcastStateChange(@roadcastState int state)1214     private void notifyBroadcastStateChange(@BroadcastState int state) {
1215         if (!mContext.getPackageName().equals(SETTINGS_PKG)) {
1216             Log.d(TAG, "Skip notifyBroadcastStateChange, not triggered by Settings.");
1217             return;
1218         }
1219         if (isWorkProfile(mContext)) {
1220             Log.d(TAG, "Skip notifyBroadcastStateChange, not triggered for work profile.");
1221             return;
1222         }
1223         Intent intent = new Intent(ACTION_LE_AUDIO_SHARING_STATE_CHANGE);
1224         intent.putExtra(EXTRA_LE_AUDIO_SHARING_STATE, state);
1225         intent.setPackage(mContext.getPackageName());
1226         Log.e(TAG, "notifyBroadcastStateChange for state = " + state);
1227         mContext.sendBroadcast(intent);
1228     }
1229 
isWorkProfile(Context context)1230     private boolean isWorkProfile(Context context) {
1231         UserManager userManager = context.getSystemService(UserManager.class);
1232         return userManager != null && userManager.isManagedProfile();
1233     }
1234 }
1235