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