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.NonNull; 23 import android.bluetooth.BluetoothAdapter; 24 import android.bluetooth.BluetoothClass; 25 import android.bluetooth.BluetoothDevice; 26 import android.bluetooth.BluetoothLeAudioContentMetadata; 27 import android.bluetooth.BluetoothLeBroadcast; 28 import android.bluetooth.BluetoothLeBroadcastAssistant; 29 import android.bluetooth.BluetoothLeBroadcastMetadata; 30 import android.bluetooth.BluetoothLeBroadcastReceiveState; 31 import android.bluetooth.BluetoothLeBroadcastSubgroup; 32 import android.bluetooth.BluetoothProfile; 33 import android.bluetooth.BluetoothProfile.ServiceListener; 34 import android.bluetooth.BluetoothStatusCodes; 35 import android.content.ContentResolver; 36 import android.content.Context; 37 import android.database.ContentObserver; 38 import android.net.Uri; 39 import android.os.Build; 40 import android.os.Handler; 41 import android.os.Looper; 42 import android.provider.Settings; 43 import android.text.TextUtils; 44 import android.util.Log; 45 import android.util.Pair; 46 47 import androidx.annotation.RequiresApi; 48 49 import com.android.settingslib.R; 50 51 import java.nio.charset.StandardCharsets; 52 import java.util.ArrayList; 53 import java.util.Arrays; 54 import java.util.Collections; 55 import java.util.List; 56 import java.util.UUID; 57 import java.util.concurrent.Executor; 58 import java.util.concurrent.Executors; 59 import java.util.concurrent.ThreadLocalRandom; 60 61 /** 62 * LocalBluetoothLeBroadcast provides an interface between the Settings app 63 * and the functionality of the local {@link BluetoothLeBroadcast}. 64 * Use the {@link BluetoothLeBroadcast.Callback} to get the result callback. 65 */ 66 public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { 67 private static final String TAG = "LocalBluetoothLeBroadcast"; 68 private static final boolean DEBUG = BluetoothUtils.D; 69 70 static final String NAME = "LE_AUDIO_BROADCAST"; 71 private static final String UNDERLINE = "_"; 72 private static final int DEFAULT_CODE_MAX = 9999; 73 private static final int DEFAULT_CODE_MIN = 1000; 74 // Order of this profile in device profiles list 75 private static final int ORDINAL = 1; 76 private static final int UNKNOWN_VALUE_PLACEHOLDER = -1; 77 private static final Uri[] SETTINGS_URIS = new Uri[]{ 78 Settings.Secure.getUriFor(Settings.Secure.BLUETOOTH_LE_BROADCAST_PROGRAM_INFO), 79 Settings.Secure.getUriFor(Settings.Secure.BLUETOOTH_LE_BROADCAST_CODE), 80 Settings.Secure.getUriFor(Settings.Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME), 81 }; 82 83 private BluetoothLeBroadcast mServiceBroadcast; 84 private BluetoothLeBroadcastAssistant mServiceBroadcastAssistant; 85 private BluetoothLeAudioContentMetadata mBluetoothLeAudioContentMetadata; 86 private BluetoothLeBroadcastMetadata mBluetoothLeBroadcastMetadata; 87 private BluetoothLeAudioContentMetadata.Builder mBuilder; 88 private int mBroadcastId = UNKNOWN_VALUE_PLACEHOLDER; 89 private String mAppSourceName = ""; 90 private String mNewAppSourceName = ""; 91 private boolean mIsBroadcastProfileReady = false; 92 private boolean mIsBroadcastAssistantProfileReady = false; 93 private String mProgramInfo; 94 private byte[] mBroadcastCode; 95 private Executor mExecutor; 96 private ContentResolver mContentResolver; 97 private ContentObserver mSettingsObserver; 98 99 private final ServiceListener mServiceListener = new ServiceListener() { 100 @Override 101 public void onServiceConnected(int profile, BluetoothProfile proxy) { 102 if (DEBUG) { 103 Log.d(TAG, "Bluetooth service connected: " + profile); 104 } 105 if ((profile == BluetoothProfile.LE_AUDIO_BROADCAST) && !mIsBroadcastProfileReady) { 106 mServiceBroadcast = (BluetoothLeBroadcast) proxy; 107 mIsBroadcastProfileReady = true; 108 registerServiceCallBack(mExecutor, mBroadcastCallback); 109 List<BluetoothLeBroadcastMetadata> metadata = getAllBroadcastMetadata(); 110 if (!metadata.isEmpty()) { 111 updateBroadcastInfoFromBroadcastMetadata(metadata.get(0)); 112 } 113 registerContentObserver(); 114 } else if ((profile == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT) 115 && !mIsBroadcastAssistantProfileReady) { 116 mIsBroadcastAssistantProfileReady = true; 117 mServiceBroadcastAssistant = (BluetoothLeBroadcastAssistant) proxy; 118 registerBroadcastAssistantCallback(mExecutor, mBroadcastAssistantCallback); 119 } 120 } 121 122 @Override 123 public void onServiceDisconnected(int profile) { 124 if (DEBUG) { 125 Log.d(TAG, "Bluetooth service disconnected"); 126 } 127 if ((profile == BluetoothProfile.LE_AUDIO_BROADCAST) && mIsBroadcastProfileReady) { 128 mIsBroadcastProfileReady = false; 129 unregisterServiceCallBack(mBroadcastCallback); 130 } 131 if ((profile == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT) 132 && mIsBroadcastAssistantProfileReady) { 133 mIsBroadcastAssistantProfileReady = false; 134 unregisterBroadcastAssistantCallback(mBroadcastAssistantCallback); 135 } 136 137 if (!mIsBroadcastAssistantProfileReady && !mIsBroadcastProfileReady) { 138 unregisterContentObserver(); 139 } 140 } 141 }; 142 143 private final BluetoothLeBroadcast.Callback mBroadcastCallback = 144 new BluetoothLeBroadcast.Callback() { 145 @Override 146 public void onBroadcastStarted(int reason, int broadcastId) { 147 if (DEBUG) { 148 Log.d(TAG, 149 "onBroadcastStarted(), reason = " + reason + ", broadcastId = " 150 + broadcastId); 151 } 152 setLatestBroadcastId(broadcastId); 153 setAppSourceName(mNewAppSourceName, /*updateContentResolver=*/ true); 154 } 155 156 @Override 157 public void onBroadcastStartFailed(int reason) { 158 if (DEBUG) { 159 Log.d(TAG, "onBroadcastStartFailed(), reason = " + reason); 160 } 161 } 162 163 @Override 164 public void onBroadcastMetadataChanged(int broadcastId, 165 @NonNull BluetoothLeBroadcastMetadata metadata) { 166 if (DEBUG) { 167 Log.d(TAG, "onBroadcastMetadataChanged(), broadcastId = " + broadcastId); 168 } 169 setLatestBluetoothLeBroadcastMetadata(metadata); 170 } 171 172 @Override 173 public void onBroadcastStopped(int reason, int broadcastId) { 174 if (DEBUG) { 175 Log.d(TAG, 176 "onBroadcastStopped(), reason = " + reason + ", broadcastId = " 177 + broadcastId); 178 } 179 180 stopLocalSourceReceivers(); 181 resetCacheInfo(); 182 } 183 184 @Override 185 public void onBroadcastStopFailed(int reason) { 186 if (DEBUG) { 187 Log.d(TAG, "onBroadcastStopFailed(), reason = " + reason); 188 } 189 } 190 191 @Override 192 public void onBroadcastUpdated(int reason, int broadcastId) { 193 if (DEBUG) { 194 Log.d(TAG, 195 "onBroadcastUpdated(), reason = " + reason + ", broadcastId = " 196 + broadcastId); 197 } 198 setLatestBroadcastId(broadcastId); 199 setAppSourceName(mNewAppSourceName, /*updateContentResolver=*/ true); 200 } 201 202 @Override 203 public void onBroadcastUpdateFailed(int reason, int broadcastId) { 204 if (DEBUG) { 205 Log.d(TAG, 206 "onBroadcastUpdateFailed(), reason = " + reason + ", broadcastId = " 207 + broadcastId); 208 } 209 } 210 211 @Override 212 public void onPlaybackStarted(int reason, int broadcastId) { 213 } 214 215 @Override 216 public void onPlaybackStopped(int reason, int broadcastId) { 217 } 218 }; 219 220 private final BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback = 221 new BluetoothLeBroadcastAssistant.Callback() { 222 @Override 223 public void onSourceAdded(@NonNull BluetoothDevice sink, int sourceId, 224 int reason) {} 225 @Override 226 public void onSearchStarted(int reason) {} 227 228 @Override 229 public void onSearchStartFailed(int reason) {} 230 231 @Override 232 public void onSearchStopped(int reason) {} 233 234 @Override 235 public void onSearchStopFailed(int reason) {} 236 237 @Override 238 public void onSourceFound(@NonNull BluetoothLeBroadcastMetadata source) {} 239 240 @Override 241 public void onSourceAddFailed(@NonNull BluetoothDevice sink, 242 @NonNull BluetoothLeBroadcastMetadata source, int reason) {} 243 244 @Override 245 public void onSourceModified(@NonNull BluetoothDevice sink, int sourceId, 246 int reason) {} 247 248 @Override 249 public void onSourceModifyFailed(@NonNull BluetoothDevice sink, int sourceId, 250 int reason) {} 251 252 @Override 253 public void onSourceRemoved(@NonNull BluetoothDevice sink, int sourceId, 254 int reason) { 255 if (DEBUG) { 256 Log.d(TAG, "onSourceRemoved(), sink = " + sink + ", reason = " 257 + reason + ", sourceId = " + sourceId); 258 } 259 } 260 261 @Override 262 public void onSourceRemoveFailed(@NonNull BluetoothDevice sink, int sourceId, 263 int reason) { 264 if (DEBUG) { 265 Log.d(TAG, "onSourceRemoveFailed(), sink = " + sink + ", reason = " 266 + reason + ", sourceId = " + sourceId); 267 } 268 } 269 270 @Override 271 public void onReceiveStateChanged(@NonNull BluetoothDevice sink, int sourceId, 272 @NonNull BluetoothLeBroadcastReceiveState state) {} 273 }; 274 275 private class BroadcastSettingsObserver extends ContentObserver { BroadcastSettingsObserver(Handler h)276 BroadcastSettingsObserver(Handler h) { 277 super(h); 278 } 279 280 @Override onChange(boolean selfChange)281 public void onChange(boolean selfChange) { 282 Log.d(TAG, "BroadcastSettingsObserver: onChange"); 283 updateBroadcastInfoFromContentProvider(); 284 } 285 } 286 LocalBluetoothLeBroadcast(Context context)287 LocalBluetoothLeBroadcast(Context context) { 288 mExecutor = Executors.newSingleThreadExecutor(); 289 mBuilder = new BluetoothLeAudioContentMetadata.Builder(); 290 mContentResolver = context.getContentResolver(); 291 Handler handler = new Handler(Looper.getMainLooper()); 292 mSettingsObserver = new BroadcastSettingsObserver(handler); 293 updateBroadcastInfoFromContentProvider(); 294 295 // Before registering callback, the constructor should finish creating the all of variables. 296 BluetoothAdapter.getDefaultAdapter() 297 .getProfileProxy(context, mServiceListener, BluetoothProfile.LE_AUDIO_BROADCAST); 298 BluetoothAdapter.getDefaultAdapter() 299 .getProfileProxy(context, mServiceListener, 300 BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT); 301 } 302 303 /** 304 * Start the LE Broadcast. If the system started the LE Broadcast, then the system calls the 305 * corresponding callback {@link BluetoothLeBroadcast.Callback}. 306 */ startBroadcast(String appSourceName, String language)307 public void startBroadcast(String appSourceName, String language) { 308 mNewAppSourceName = appSourceName; 309 if (mServiceBroadcast == null) { 310 Log.d(TAG, "The BluetoothLeBroadcast is null when starting the broadcast."); 311 return; 312 } 313 String programInfo = getProgramInfo(); 314 if (DEBUG) { 315 Log.d(TAG, 316 "startBroadcast: language = " + language + " ,programInfo = " + programInfo); 317 } 318 buildContentMetadata(language, programInfo); 319 mServiceBroadcast.startBroadcast(mBluetoothLeAudioContentMetadata, 320 (mBroadcastCode != null && mBroadcastCode.length > 0) ? mBroadcastCode : null); 321 } 322 getProgramInfo()323 public String getProgramInfo() { 324 return mProgramInfo; 325 } 326 setProgramInfo(String programInfo)327 public void setProgramInfo(String programInfo) { 328 setProgramInfo(programInfo, /*updateContentResolver=*/ true); 329 } 330 setProgramInfo(String programInfo, boolean updateContentResolver)331 private void setProgramInfo(String programInfo, boolean updateContentResolver) { 332 if (TextUtils.isEmpty(programInfo)) { 333 Log.d(TAG, "setProgramInfo: programInfo is null or empty"); 334 return; 335 } 336 if (mProgramInfo != null && TextUtils.equals(mProgramInfo, programInfo)) { 337 Log.d(TAG, "setProgramInfo: programInfo is not changed"); 338 return; 339 } 340 Log.d(TAG, "setProgramInfo: " + programInfo); 341 mProgramInfo = programInfo; 342 if (updateContentResolver) { 343 if (mContentResolver == null) { 344 Log.d(TAG, "mContentResolver is null"); 345 return; 346 } 347 Settings.Secure.putString(mContentResolver, 348 Settings.Secure.BLUETOOTH_LE_BROADCAST_PROGRAM_INFO, programInfo); 349 } 350 } 351 getBroadcastCode()352 public byte[] getBroadcastCode() { 353 return mBroadcastCode; 354 } 355 setBroadcastCode(byte[] broadcastCode)356 public void setBroadcastCode(byte[] broadcastCode) { 357 setBroadcastCode(broadcastCode, /*updateContentResolver=*/ true); 358 } 359 setBroadcastCode(byte[] broadcastCode, boolean updateContentResolver)360 private void setBroadcastCode(byte[] broadcastCode, boolean updateContentResolver) { 361 if (broadcastCode == null) { 362 Log.d(TAG, "setBroadcastCode: broadcastCode is null"); 363 return; 364 } 365 if (mBroadcastCode != null && Arrays.equals(broadcastCode, mBroadcastCode)) { 366 Log.d(TAG, "setBroadcastCode: broadcastCode is not changed"); 367 return; 368 } 369 mBroadcastCode = broadcastCode; 370 if (updateContentResolver) { 371 if (mContentResolver == null) { 372 Log.d(TAG, "mContentResolver is null"); 373 return; 374 } 375 Settings.Secure.putString(mContentResolver, Settings.Secure.BLUETOOTH_LE_BROADCAST_CODE, 376 new String(broadcastCode, StandardCharsets.UTF_8)); 377 } 378 } 379 setLatestBroadcastId(int broadcastId)380 private void setLatestBroadcastId(int broadcastId) { 381 Log.d(TAG, "setLatestBroadcastId: mBroadcastId is " + broadcastId); 382 mBroadcastId = broadcastId; 383 } 384 getLatestBroadcastId()385 public int getLatestBroadcastId() { 386 return mBroadcastId; 387 } 388 setAppSourceName(String appSourceName, boolean updateContentResolver)389 private void setAppSourceName(String appSourceName, boolean updateContentResolver) { 390 if (TextUtils.isEmpty(appSourceName)) { 391 appSourceName = ""; 392 } 393 if (mAppSourceName != null && TextUtils.equals(mAppSourceName, appSourceName)) { 394 Log.d(TAG, "setAppSourceName: appSourceName is not changed"); 395 return; 396 } 397 mAppSourceName = appSourceName; 398 mNewAppSourceName = ""; 399 if (updateContentResolver) { 400 if (mContentResolver == null) { 401 Log.d(TAG, "mContentResolver is null"); 402 return; 403 } 404 Settings.Secure.putString(mContentResolver, 405 Settings.Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME, mAppSourceName); 406 } 407 } 408 getAppSourceName()409 public String getAppSourceName() { 410 return mAppSourceName; 411 } 412 setLatestBluetoothLeBroadcastMetadata( BluetoothLeBroadcastMetadata bluetoothLeBroadcastMetadata)413 private void setLatestBluetoothLeBroadcastMetadata( 414 BluetoothLeBroadcastMetadata bluetoothLeBroadcastMetadata) { 415 if (bluetoothLeBroadcastMetadata != null 416 && bluetoothLeBroadcastMetadata.getBroadcastId() == mBroadcastId) { 417 mBluetoothLeBroadcastMetadata = bluetoothLeBroadcastMetadata; 418 updateBroadcastInfoFromBroadcastMetadata(bluetoothLeBroadcastMetadata); 419 } 420 } 421 getLatestBluetoothLeBroadcastMetadata()422 public BluetoothLeBroadcastMetadata getLatestBluetoothLeBroadcastMetadata() { 423 if (mServiceBroadcast == null) { 424 Log.d(TAG, "The BluetoothLeBroadcast is null"); 425 return null; 426 } 427 if (mBluetoothLeBroadcastMetadata == null) { 428 final List<BluetoothLeBroadcastMetadata> metadataList = 429 mServiceBroadcast.getAllBroadcastMetadata(); 430 mBluetoothLeBroadcastMetadata = metadataList.stream() 431 .filter(i -> i.getBroadcastId() == mBroadcastId) 432 .findFirst() 433 .orElse(null); 434 } 435 return mBluetoothLeBroadcastMetadata; 436 } 437 updateBroadcastInfoFromContentProvider()438 private void updateBroadcastInfoFromContentProvider() { 439 if (mContentResolver == null) { 440 Log.d(TAG, "updateBroadcastInfoFromContentProvider: mContentResolver is null"); 441 return; 442 } 443 String programInfo = Settings.Secure.getString(mContentResolver, 444 Settings.Secure.BLUETOOTH_LE_BROADCAST_PROGRAM_INFO); 445 if (programInfo == null) { 446 programInfo = getDefaultValueOfProgramInfo(); 447 } 448 setProgramInfo(programInfo, /*updateContentResolver=*/ false); 449 450 String prefBroadcastCode = Settings.Secure.getString(mContentResolver, 451 Settings.Secure.BLUETOOTH_LE_BROADCAST_CODE); 452 byte[] broadcastCode = (prefBroadcastCode == null) ? getDefaultValueOfBroadcastCode() 453 : prefBroadcastCode.getBytes(StandardCharsets.UTF_8); 454 setBroadcastCode(broadcastCode, /*updateContentResolver=*/ false); 455 456 String appSourceName = Settings.Secure.getString(mContentResolver, 457 Settings.Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME); 458 setAppSourceName(appSourceName, /*updateContentResolver=*/ false); 459 } 460 updateBroadcastInfoFromBroadcastMetadata( BluetoothLeBroadcastMetadata bluetoothLeBroadcastMetadata)461 private void updateBroadcastInfoFromBroadcastMetadata( 462 BluetoothLeBroadcastMetadata bluetoothLeBroadcastMetadata) { 463 if (bluetoothLeBroadcastMetadata == null) { 464 Log.d(TAG, "The bluetoothLeBroadcastMetadata is null"); 465 return; 466 } 467 setBroadcastCode(bluetoothLeBroadcastMetadata.getBroadcastCode()); 468 setLatestBroadcastId(bluetoothLeBroadcastMetadata.getBroadcastId()); 469 470 List<BluetoothLeBroadcastSubgroup> subgroup = bluetoothLeBroadcastMetadata.getSubgroups(); 471 if (subgroup == null || subgroup.size() < 1) { 472 Log.d(TAG, "The subgroup is not valid value"); 473 return; 474 } 475 BluetoothLeAudioContentMetadata contentMetadata = subgroup.get(0).getContentMetadata(); 476 setProgramInfo(contentMetadata.getProgramInfo()); 477 setAppSourceName(getAppSourceName(), /*updateContentResolver=*/ true); 478 } 479 480 /** 481 * Stop the latest LE Broadcast. If the system stopped the LE Broadcast, then the system 482 * calls the corresponding callback {@link BluetoothLeBroadcast.Callback}. 483 */ stopLatestBroadcast()484 public void stopLatestBroadcast() { 485 stopBroadcast(mBroadcastId); 486 } 487 488 /** 489 * Stop the LE Broadcast. If the system stopped the LE Broadcast, then the system calls the 490 * corresponding callback {@link BluetoothLeBroadcast.Callback}. 491 */ stopBroadcast(int broadcastId)492 public void stopBroadcast(int broadcastId) { 493 if (mServiceBroadcast == null) { 494 Log.d(TAG, "The BluetoothLeBroadcast is null when stopping the broadcast."); 495 return; 496 } 497 if (DEBUG) { 498 Log.d(TAG, "stopBroadcast()"); 499 } 500 mServiceBroadcast.stopBroadcast(broadcastId); 501 } 502 503 /** 504 * Update the LE Broadcast. If the system stopped the LE Broadcast, then the system calls the 505 * corresponding callback {@link BluetoothLeBroadcast.Callback}. 506 */ updateBroadcast(String appSourceName, String language)507 public void updateBroadcast(String appSourceName, String language) { 508 if (mServiceBroadcast == null) { 509 Log.d(TAG, "The BluetoothLeBroadcast is null when updating the broadcast."); 510 return; 511 } 512 String programInfo = getProgramInfo(); 513 if (DEBUG) { 514 Log.d(TAG, 515 "updateBroadcast: language = " + language + " ,programInfo = " + programInfo); 516 } 517 mNewAppSourceName = appSourceName; 518 mBluetoothLeAudioContentMetadata = mBuilder.setProgramInfo(programInfo).build(); 519 mServiceBroadcast.updateBroadcast(mBroadcastId, mBluetoothLeAudioContentMetadata); 520 } 521 registerServiceCallBack(@onNull @allbackExecutor Executor executor, @NonNull BluetoothLeBroadcast.Callback callback)522 public void registerServiceCallBack(@NonNull @CallbackExecutor Executor executor, 523 @NonNull BluetoothLeBroadcast.Callback callback) { 524 if (mServiceBroadcast == null) { 525 Log.d(TAG, "The BluetoothLeBroadcast is null."); 526 return; 527 } 528 529 mServiceBroadcast.registerCallback(executor, callback); 530 } 531 532 /** 533 * Register Broadcast Assistant Callbacks to track it's state and receivers 534 * 535 * @param executor Executor object for callback 536 * @param callback Callback object to be registered 537 */ registerBroadcastAssistantCallback(@onNull @allbackExecutor Executor executor, @NonNull BluetoothLeBroadcastAssistant.Callback callback)538 public void registerBroadcastAssistantCallback(@NonNull @CallbackExecutor Executor executor, 539 @NonNull BluetoothLeBroadcastAssistant.Callback callback) { 540 if (mServiceBroadcastAssistant == null) { 541 Log.d(TAG, "The BluetoothLeBroadcastAssisntant is null."); 542 return; 543 } 544 545 mServiceBroadcastAssistant.registerCallback(executor, callback); 546 } 547 unregisterServiceCallBack(@onNull BluetoothLeBroadcast.Callback callback)548 public void unregisterServiceCallBack(@NonNull BluetoothLeBroadcast.Callback callback) { 549 if (mServiceBroadcast == null) { 550 Log.d(TAG, "The BluetoothLeBroadcast is null."); 551 return; 552 } 553 554 mServiceBroadcast.unregisterCallback(callback); 555 } 556 557 /** 558 * Unregister previousely registered Broadcast Assistant Callbacks 559 * 560 * @param callback Callback object to be unregistered 561 */ unregisterBroadcastAssistantCallback( @onNull BluetoothLeBroadcastAssistant.Callback callback)562 public void unregisterBroadcastAssistantCallback( 563 @NonNull BluetoothLeBroadcastAssistant.Callback callback) { 564 if (mServiceBroadcastAssistant == null) { 565 Log.d(TAG, "The BluetoothLeBroadcastAssisntant is null."); 566 return; 567 } 568 569 mServiceBroadcastAssistant.unregisterCallback(callback); 570 } 571 buildContentMetadata(String language, String programInfo)572 private void buildContentMetadata(String language, String programInfo) { 573 mBluetoothLeAudioContentMetadata = mBuilder.setLanguage(language).setProgramInfo( 574 programInfo).build(); 575 } 576 getLocalBluetoothLeBroadcastMetaData()577 public LocalBluetoothLeBroadcastMetadata getLocalBluetoothLeBroadcastMetaData() { 578 final BluetoothLeBroadcastMetadata metadata = getLatestBluetoothLeBroadcastMetadata(); 579 if (metadata == null) { 580 Log.d(TAG, "The BluetoothLeBroadcastMetadata is null."); 581 return null; 582 } 583 return new LocalBluetoothLeBroadcastMetadata(metadata); 584 } 585 isProfileReady()586 public boolean isProfileReady() { 587 return mIsBroadcastProfileReady; 588 } 589 590 @Override getProfileId()591 public int getProfileId() { 592 return BluetoothProfile.LE_AUDIO_BROADCAST; 593 } 594 accessProfileEnabled()595 public boolean accessProfileEnabled() { 596 return false; 597 } 598 isAutoConnectable()599 public boolean isAutoConnectable() { 600 return true; 601 } 602 603 /** 604 * Not supported since LE Audio Broadcasts do not establish a connection. 605 */ getConnectionStatus(BluetoothDevice device)606 public int getConnectionStatus(BluetoothDevice device) { 607 if (mServiceBroadcast == null) { 608 return BluetoothProfile.STATE_DISCONNECTED; 609 } 610 // LE Audio Broadcasts are not connection-oriented. 611 return mServiceBroadcast.getConnectionState(device); 612 } 613 614 /** 615 * Not supported since LE Audio Broadcasts do not establish a connection. 616 */ getConnectedDevices()617 public List<BluetoothDevice> getConnectedDevices() { 618 if (mServiceBroadcast == null) { 619 return new ArrayList<BluetoothDevice>(0); 620 } 621 // LE Audio Broadcasts are not connection-oriented. 622 return mServiceBroadcast.getConnectedDevices(); 623 } 624 625 public @NonNull getAllBroadcastMetadata()626 List<BluetoothLeBroadcastMetadata> getAllBroadcastMetadata() { 627 if (mServiceBroadcast == null) { 628 Log.d(TAG, "The BluetoothLeBroadcast is null."); 629 return Collections.emptyList(); 630 } 631 632 return mServiceBroadcast.getAllBroadcastMetadata(); 633 } 634 isEnabled(BluetoothDevice device)635 public boolean isEnabled(BluetoothDevice device) { 636 if (mServiceBroadcast == null) { 637 return false; 638 } 639 640 return !mServiceBroadcast.getAllBroadcastMetadata().isEmpty(); 641 } 642 643 /** 644 * Service does not provide method to get/set policy. 645 */ getConnectionPolicy(BluetoothDevice device)646 public int getConnectionPolicy(BluetoothDevice device) { 647 return CONNECTION_POLICY_FORBIDDEN; 648 } 649 650 /** 651 * Service does not provide "setEnabled" method. Please use {@link #startBroadcast}, 652 * {@link #stopBroadcast()} or {@link #updateBroadcast(String, String)} 653 */ setEnabled(BluetoothDevice device, boolean enabled)654 public boolean setEnabled(BluetoothDevice device, boolean enabled) { 655 return false; 656 } 657 toString()658 public String toString() { 659 return NAME; 660 } 661 getOrdinal()662 public int getOrdinal() { 663 return ORDINAL; 664 } 665 getNameResource(BluetoothDevice device)666 public int getNameResource(BluetoothDevice device) { 667 return R.string.summary_empty; 668 } 669 getSummaryResourceForDevice(BluetoothDevice device)670 public int getSummaryResourceForDevice(BluetoothDevice device) { 671 int state = getConnectionStatus(device); 672 return BluetoothUtils.getConnectionStateSummary(state); 673 } 674 getDrawableResource(BluetoothClass btClass)675 public int getDrawableResource(BluetoothClass btClass) { 676 return 0; 677 } 678 679 @RequiresApi(Build.VERSION_CODES.S) finalize()680 protected void finalize() { 681 if (DEBUG) { 682 Log.d(TAG, "finalize()"); 683 } 684 if (mServiceBroadcast != null) { 685 try { 686 BluetoothAdapter.getDefaultAdapter().closeProfileProxy( 687 BluetoothProfile.LE_AUDIO_BROADCAST, 688 mServiceBroadcast); 689 mServiceBroadcast = null; 690 } catch (Throwable t) { 691 Log.w(TAG, "Error cleaning up LeAudio proxy", t); 692 } 693 } 694 } 695 getDefaultValueOfProgramInfo()696 private String getDefaultValueOfProgramInfo() { 697 //set the default value; 698 int postfix = ThreadLocalRandom.current().nextInt(DEFAULT_CODE_MIN, DEFAULT_CODE_MAX); 699 return BluetoothAdapter.getDefaultAdapter().getName() + UNDERLINE + postfix; 700 } 701 getDefaultValueOfBroadcastCode()702 private byte[] getDefaultValueOfBroadcastCode() { 703 //set the default value; 704 return generateRandomPassword().getBytes(StandardCharsets.UTF_8); 705 } 706 resetCacheInfo()707 private void resetCacheInfo() { 708 if (DEBUG) { 709 Log.d(TAG, "resetCacheInfo:"); 710 } 711 setAppSourceName("", /*updateContentResolver=*/ true); 712 mBluetoothLeBroadcastMetadata = null; 713 mBroadcastId = UNKNOWN_VALUE_PLACEHOLDER; 714 } 715 generateRandomPassword()716 private String generateRandomPassword() { 717 String randomUUID = UUID.randomUUID().toString(); 718 //first 12 chars from xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx 719 return randomUUID.substring(0, 8) + randomUUID.substring(9, 13); 720 } 721 registerContentObserver()722 private void registerContentObserver() { 723 if (mContentResolver == null) { 724 Log.d(TAG, "mContentResolver is null"); 725 return; 726 } 727 for (Uri uri : SETTINGS_URIS) { 728 mContentResolver.registerContentObserver(uri, false, mSettingsObserver); 729 } 730 } 731 unregisterContentObserver()732 private void unregisterContentObserver() { 733 if (mContentResolver == null) { 734 Log.d(TAG, "mContentResolver is null"); 735 return; 736 } 737 mContentResolver.unregisterContentObserver(mSettingsObserver); 738 } 739 stopLocalSourceReceivers()740 private void stopLocalSourceReceivers() { 741 if (DEBUG) { 742 Log.d(TAG, "stopLocalSourceReceivers()"); 743 } 744 for (BluetoothDevice device : mServiceBroadcastAssistant.getConnectedDevices()) { 745 for (BluetoothLeBroadcastReceiveState receiveState : 746 mServiceBroadcastAssistant.getAllSources(device)) { 747 /* Check if local/last broadcast is the synced one */ 748 int localBroadcastId = getLatestBroadcastId(); 749 if (receiveState.getBroadcastId() != localBroadcastId) continue; 750 751 mServiceBroadcastAssistant.removeSource(device, receiveState.getSourceId()); 752 } 753 } 754 } 755 756 } 757