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