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