1 /* 2 * Copyright (C) 2024 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.settings.connecteddevice.audiosharing; 18 19 import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.EXTRA_START_LE_AUDIO_SHARING; 20 21 import android.app.settings.SettingsEnums; 22 import android.bluetooth.BluetoothCsipSetCoordinator; 23 import android.bluetooth.BluetoothDevice; 24 import android.bluetooth.BluetoothLeBroadcast; 25 import android.bluetooth.BluetoothLeBroadcastMetadata; 26 import android.bluetooth.BluetoothLeBroadcastReceiveState; 27 import android.content.Context; 28 import android.media.AudioManager; 29 import android.os.Bundle; 30 import android.util.Log; 31 import android.util.Pair; 32 33 import androidx.annotation.NonNull; 34 import androidx.annotation.Nullable; 35 import androidx.annotation.VisibleForTesting; 36 import androidx.fragment.app.DialogFragment; 37 import androidx.fragment.app.Fragment; 38 39 import com.android.settings.R; 40 import com.android.settings.bluetooth.Utils; 41 import com.android.settings.core.SubSettingLauncher; 42 import com.android.settings.dashboard.DashboardFragment; 43 import com.android.settings.overlay.FeatureFactory; 44 import com.android.settingslib.bluetooth.BluetoothUtils; 45 import com.android.settingslib.bluetooth.CachedBluetoothDevice; 46 import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; 47 import com.android.settingslib.bluetooth.LeAudioProfile; 48 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast; 49 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant; 50 import com.android.settingslib.bluetooth.LocalBluetoothManager; 51 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; 52 import com.android.settingslib.flags.Flags; 53 import com.android.settingslib.utils.ThreadUtils; 54 55 import com.google.common.collect.ImmutableList; 56 57 import java.util.ArrayList; 58 import java.util.List; 59 import java.util.Map; 60 import java.util.concurrent.Executor; 61 62 public class AudioSharingDialogHandler { 63 private static final String TAG = "AudioSharingDlgHandler"; 64 private final Context mContext; 65 private final Fragment mHostFragment; 66 @Nullable private final LocalBluetoothManager mLocalBtManager; 67 @Nullable private final CachedBluetoothDeviceManager mDeviceManager; 68 @Nullable private final LocalBluetoothLeBroadcast mBroadcast; 69 @Nullable private final LocalBluetoothLeBroadcastAssistant mAssistant; 70 @Nullable private final AudioManager mAudioManager; 71 private final MetricsFeatureProvider mMetricsFeatureProvider; 72 private boolean mIsStoppingBroadcast = false; 73 74 @VisibleForTesting 75 final BluetoothLeBroadcast.Callback mBroadcastCallback = 76 new BluetoothLeBroadcast.Callback() { 77 @Override 78 public void onBroadcastStarted(int reason, int broadcastId) { 79 Log.d( 80 TAG, 81 "onBroadcastStarted(), reason = " 82 + reason 83 + ", broadcastId = " 84 + broadcastId); 85 } 86 87 @Override 88 public void onBroadcastStartFailed(int reason) { 89 Log.d(TAG, "onBroadcastStartFailed(), reason = " + reason); 90 } 91 92 @Override 93 public void onBroadcastMetadataChanged( 94 int broadcastId, @NonNull BluetoothLeBroadcastMetadata metadata) { 95 Log.d( 96 TAG, 97 "onBroadcastMetadataChanged(), broadcastId = " 98 + broadcastId 99 + ", metadata = " 100 + metadata); 101 } 102 103 @Override 104 public void onBroadcastStopped(int reason, int broadcastId) { 105 Log.d( 106 TAG, 107 "onBroadcastStopped(), reason = " 108 + reason 109 + ", broadcastId = " 110 + broadcastId); 111 AudioSharingUtils.toastMessage( 112 mContext, 113 mContext.getString(R.string.audio_sharing_sharing_stopped_label)); 114 mIsStoppingBroadcast = false; 115 } 116 117 @Override 118 public void onBroadcastStopFailed(int reason) { 119 Log.d(TAG, "onBroadcastStopFailed(), reason = " + reason); 120 if (mIsStoppingBroadcast) { 121 mMetricsFeatureProvider.action( 122 mContext, 123 SettingsEnums.ACTION_AUDIO_SHARING_STOP_FAILED, 124 SettingsEnums.SETTINGS_CONNECTED_DEVICE_CATEGORY); 125 AudioSharingUtils.toastMessage( 126 mContext, "Fail to stop broadcast, reason " + reason); 127 mIsStoppingBroadcast = false; 128 } 129 } 130 131 @Override 132 public void onBroadcastUpdated(int reason, int broadcastId) {} 133 134 @Override 135 public void onBroadcastUpdateFailed(int reason, int broadcastId) {} 136 137 @Override 138 public void onPlaybackStarted(int reason, int broadcastId) { 139 Log.d( 140 TAG, 141 "onPlaybackStarted(), reason = " 142 + reason 143 + ", broadcastId = " 144 + broadcastId); 145 } 146 147 @Override 148 public void onPlaybackStopped(int reason, int broadcastId) {} 149 }; 150 AudioSharingDialogHandler(@onNull Context context, @NonNull Fragment fragment)151 public AudioSharingDialogHandler(@NonNull Context context, @NonNull Fragment fragment) { 152 mContext = context; 153 mHostFragment = fragment; 154 mLocalBtManager = Utils.getLocalBluetoothManager(context); 155 mDeviceManager = mLocalBtManager != null ? mLocalBtManager.getCachedDeviceManager() : null; 156 mBroadcast = 157 mLocalBtManager != null 158 ? mLocalBtManager.getProfileManager().getLeAudioBroadcastProfile() 159 : null; 160 mAssistant = 161 mLocalBtManager != null 162 ? mLocalBtManager.getProfileManager().getLeAudioBroadcastAssistantProfile() 163 : null; 164 mAudioManager = context.getSystemService(AudioManager.class); 165 mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider(); 166 } 167 168 /** Register callbacks for dialog handler */ registerCallbacks(Executor executor)169 public void registerCallbacks(Executor executor) { 170 if (mBroadcast != null) { 171 mBroadcast.registerServiceCallBack(executor, mBroadcastCallback); 172 } 173 } 174 175 /** Unregister callbacks for dialog handler */ unregisterCallbacks()176 public void unregisterCallbacks() { 177 if (mBroadcast != null) { 178 mBroadcast.unregisterServiceCallBack(mBroadcastCallback); 179 } 180 } 181 182 /** 183 * Handle dialog pop-up logic when device is connected. 184 * @param cachedDevice The target {@link CachedBluetoothDevice} to handle for 185 * @param userTriggered If the device is connected by user 186 * @return If a dialog is popped up 187 */ handleDeviceConnected( @onNull CachedBluetoothDevice cachedDevice, boolean userTriggered)188 public boolean handleDeviceConnected( 189 @NonNull CachedBluetoothDevice cachedDevice, boolean userTriggered) { 190 String anonymizedAddress = cachedDevice.getDevice().getAnonymizedAddress(); 191 if (mAudioManager != null) { 192 int audioMode = mAudioManager.getMode(); 193 if (audioMode == AudioManager.MODE_RINGTONE 194 || audioMode == AudioManager.MODE_IN_CALL 195 || audioMode == AudioManager.MODE_IN_COMMUNICATION) { 196 Log.d(TAG, "Skip handleDeviceConnected, audio mode = " + audioMode); 197 // TODO: add metric for this case 198 if (userTriggered) { 199 // If this method is called with user triggered, e.g. manual click on the 200 // "Connected devices" page, we need call setActive for the device, since user 201 // intend to switch active device for the call. 202 cachedDevice.setActive(); 203 AudioSharingUtils.setUserPreferredPrimary(mContext, cachedDevice); 204 } 205 return false; 206 } 207 } 208 boolean isBroadcasting = isBroadcasting(); 209 boolean isLeAudioSupported = BluetoothUtils.isLeAudioSupported(cachedDevice); 210 if (!isLeAudioSupported) { 211 Log.d(TAG, "Handle non LE audio device connected, device = " + anonymizedAddress); 212 // Handle connected ineligible (non LE audio) remote device 213 return handleNonLeAudioDeviceConnected(cachedDevice, isBroadcasting, userTriggered); 214 } else { 215 Log.d(TAG, "Handle LE audio device connected, device = " + anonymizedAddress); 216 // Handle connected eligible (LE audio) remote device 217 return handleLeAudioDeviceConnected(cachedDevice, isBroadcasting, userTriggered); 218 } 219 } 220 handleNonLeAudioDeviceConnected( @onNull CachedBluetoothDevice cachedDevice, boolean isBroadcasting, boolean userTriggered)221 private boolean handleNonLeAudioDeviceConnected( 222 @NonNull CachedBluetoothDevice cachedDevice, 223 boolean isBroadcasting, 224 boolean userTriggered) { 225 if (isBroadcasting) { 226 // Show stop audio sharing dialog when an ineligible (non LE audio) remote device 227 // connected during a sharing session. 228 Map<Integer, List<BluetoothDevice>> groupedDevices = 229 AudioSharingUtils.fetchConnectedDevicesByGroupId(mLocalBtManager); 230 List<AudioSharingDeviceItem> deviceItemsInSharingSession = 231 AudioSharingUtils.buildOrderedConnectedLeadAudioSharingDeviceItem( 232 mLocalBtManager, groupedDevices, /* filterByInSharing= */ true); 233 AudioSharingStopDialogFragment.DialogEventListener listener = 234 () -> { 235 if (mLocalBtManager != null && (Flags.adoptPrimaryGroupManagementApi() || ( 236 mContext != null && Flags.audioSharingDeveloperOption() 237 && BluetoothUtils.getAudioSharingPreviewValue( 238 mContext.getContentResolver())))) { 239 LeAudioProfile profile = 240 mLocalBtManager.getProfileManager().getLeAudioProfile(); 241 if (profile != null) { 242 profile.setBroadcastToUnicastFallbackGroup( 243 BluetoothCsipSetCoordinator.GROUP_ID_INVALID); 244 } 245 } 246 cachedDevice.setActive(); 247 mIsStoppingBroadcast = true; 248 AudioSharingUtils.stopBroadcasting(mLocalBtManager); 249 }; 250 Pair<Integer, Object>[] eventData = 251 AudioSharingUtils.buildAudioSharingDialogEventData( 252 SettingsEnums.SETTINGS_CONNECTED_DEVICE_CATEGORY, 253 SettingsEnums.DIALOG_STOP_AUDIO_SHARING, 254 userTriggered, 255 deviceItemsInSharingSession.size(), 256 /* candidateDeviceCount= */ 0); 257 closeOpeningDialogsOtherThan(AudioSharingStopDialogFragment.tag()); 258 return AudioSharingStopDialogFragment.show( 259 mHostFragment, 260 deviceItemsInSharingSession, 261 cachedDevice, 262 listener, 263 eventData); 264 } else { 265 if (userTriggered) { 266 cachedDevice.setActive(); 267 } 268 // Do nothing for ineligible (non LE audio) remote device when no sharing session. 269 Log.d( 270 TAG, 271 "Ignore onProfileConnectionStateChanged for non LE audio without" 272 + " sharing session"); 273 return false; 274 } 275 } 276 handleLeAudioDeviceConnected( @onNull CachedBluetoothDevice cachedDevice, boolean isBroadcasting, boolean userTriggered)277 private boolean handleLeAudioDeviceConnected( 278 @NonNull CachedBluetoothDevice cachedDevice, 279 boolean isBroadcasting, 280 boolean userTriggered) { 281 Map<Integer, List<BluetoothDevice>> groupedDevices = 282 AudioSharingUtils.fetchConnectedDevicesByGroupId(mLocalBtManager); 283 BluetoothDevice btDevice = cachedDevice.getDevice(); 284 String deviceAddress = btDevice == null ? "" : btDevice.getAnonymizedAddress(); 285 int groupId = BluetoothUtils.getGroupId(cachedDevice); 286 if (isBroadcasting) { 287 // If another device within the same is already in the sharing session, add source to 288 // the device automatically. 289 if (groupedDevices.containsKey(groupId) 290 && groupedDevices.get(groupId).stream() 291 .anyMatch( 292 device -> 293 BluetoothUtils.hasConnectedBroadcastSourceForBtDevice( 294 device, mLocalBtManager))) { 295 Log.d(TAG, "Auto add sink with the same group to the sharing: " + deviceAddress); 296 if (mAssistant != null && mBroadcast != null) { 297 mAssistant.addSource( 298 btDevice, 299 mBroadcast.getLatestBluetoothLeBroadcastMetadata(), 300 /* isGroupOp= */ false); 301 } 302 return false; 303 } 304 305 // Show audio sharing switch or join dialog according to device count in the sharing 306 // session. 307 List<AudioSharingDeviceItem> deviceItemsInSharingSession = 308 AudioSharingUtils.buildOrderedConnectedLeadAudioSharingDeviceItem( 309 mLocalBtManager, groupedDevices, /* filterByInSharing= */ true); 310 // Show audio sharing switch dialog when the third eligible (LE audio) remote device 311 // connected during a sharing session. 312 if (deviceItemsInSharingSession.size() >= 2) { 313 AudioSharingDisconnectDialogFragment.DialogEventListener listener = 314 (AudioSharingDeviceItem item) -> { 315 // Remove all sources from the device user clicked 316 removeSourceForGroup(item.getGroupId(), groupedDevices); 317 // Add current broadcast to the latest connected device 318 addSourceForGroup(groupId, groupedDevices); 319 }; 320 Pair<Integer, Object>[] eventData = 321 AudioSharingUtils.buildAudioSharingDialogEventData( 322 SettingsEnums.SETTINGS_CONNECTED_DEVICE_CATEGORY, 323 SettingsEnums.DIALOG_AUDIO_SHARING_SWITCH_DEVICE, 324 userTriggered, 325 deviceItemsInSharingSession.size(), 326 /* candidateDeviceCount= */ 1); 327 closeOpeningDialogsOtherThan( 328 AudioSharingDisconnectDialogFragment.tag()); 329 Log.d(TAG, "Show disconnect dialog, device = " + deviceAddress); 330 return AudioSharingDisconnectDialogFragment.show( 331 mHostFragment, 332 deviceItemsInSharingSession, 333 cachedDevice, 334 listener, 335 eventData); 336 } else { 337 // Show audio sharing join dialog when the first or second eligible (LE audio) 338 // remote device connected during a sharing session. 339 AudioSharingJoinDialogFragment.DialogEventListener listener = 340 new AudioSharingJoinDialogFragment.DialogEventListener() { 341 @Override 342 public void onShareClick() { 343 addSourceForGroup(groupId, groupedDevices); 344 } 345 346 @Override 347 public void onCancelClick() {} 348 }; 349 Pair<Integer, Object>[] eventData = 350 AudioSharingUtils.buildAudioSharingDialogEventData( 351 SettingsEnums.SETTINGS_CONNECTED_DEVICE_CATEGORY, 352 SettingsEnums.DIALOG_AUDIO_SHARING_ADD_DEVICE, 353 userTriggered, 354 deviceItemsInSharingSession.size(), 355 /* candidateDeviceCount= */ 1); 356 closeOpeningDialogsOtherThan(AudioSharingJoinDialogFragment.tag()); 357 Log.d(TAG, "Show join dialog, device = " + deviceAddress); 358 return AudioSharingJoinDialogFragment.show( 359 mHostFragment, 360 deviceItemsInSharingSession, 361 cachedDevice, 362 listener, 363 eventData); 364 } 365 } else { 366 // Build a list of AudioSharingDeviceItem for connected devices other than cachedDevice. 367 List<AudioSharingDeviceItem> deviceItems = new ArrayList<>(); 368 for (Map.Entry<Integer, List<BluetoothDevice>> entry : groupedDevices.entrySet()) { 369 if (entry.getKey() == groupId) continue; 370 // Use random device in the group within the sharing session to represent the group. 371 for (BluetoothDevice device : entry.getValue()) { 372 CachedBluetoothDevice cDevice = 373 mDeviceManager != null ? mDeviceManager.findDevice(device) : null; 374 if (cDevice != null) { 375 deviceItems.add(AudioSharingUtils.buildAudioSharingDeviceItem(cDevice)); 376 break; 377 } 378 } 379 } 380 // Show audio sharing join dialog when the second eligible (LE audio) remote 381 // device connect and no sharing session. 382 if (groupedDevices.size() == 2 && deviceItems.size() == 1) { 383 AudioSharingJoinDialogFragment.DialogEventListener listener = 384 new AudioSharingJoinDialogFragment.DialogEventListener() { 385 @Override 386 public void onShareClick() { 387 Bundle args = new Bundle(); 388 args.putBoolean(EXTRA_START_LE_AUDIO_SHARING, true); 389 new SubSettingLauncher(mContext) 390 .setDestination( 391 AudioSharingDashboardFragment.class.getName()) 392 .setSourceMetricsCategory( 393 (mHostFragment instanceof DashboardFragment) 394 ? ((DashboardFragment) mHostFragment) 395 .getMetricsCategory() 396 : SettingsEnums.PAGE_UNKNOWN) 397 .setArguments(args) 398 .launch(); 399 } 400 401 @Override 402 public void onCancelClick() { 403 if (userTriggered) { 404 cachedDevice.setActive(); 405 } 406 } 407 }; 408 409 Pair<Integer, Object>[] eventData = 410 AudioSharingUtils.buildAudioSharingDialogEventData( 411 SettingsEnums.SETTINGS_CONNECTED_DEVICE_CATEGORY, 412 SettingsEnums.DIALOG_START_AUDIO_SHARING, 413 userTriggered, 414 /* deviceCountInSharing= */ 0, 415 /* candidateDeviceCount= */ 2); 416 closeOpeningDialogsOtherThan(AudioSharingJoinDialogFragment.tag()); 417 Log.d(TAG, "Show start dialog, device = " + deviceAddress); 418 return AudioSharingJoinDialogFragment.show( 419 mHostFragment, deviceItems, cachedDevice, listener, eventData); 420 } else if (userTriggered) { 421 cachedDevice.setActive(); 422 Log.d(TAG, "Set active device = " + deviceAddress); 423 return false; 424 } else { 425 Log.d(TAG, "Fail to handle LE audio device connected, device = " + deviceAddress); 426 return false; 427 } 428 } 429 } 430 431 /** Close opening dialogs other than the given tag */ closeOpeningDialogsOtherThan(String tag)432 public void closeOpeningDialogsOtherThan(String tag) { 433 if (mHostFragment == null) return; 434 AudioSharingUtils.postOnMainThread( 435 mContext, 436 () -> { 437 List<Fragment> fragments; 438 try { 439 fragments = mHostFragment.getChildFragmentManager().getFragments(); 440 } catch (IllegalStateException e) { 441 Log.d(TAG, "Fail to closeOpeningDialogsOtherThan " + tag + ": " 442 + e.getMessage()); 443 return; 444 } 445 for (Fragment fragment : fragments) { 446 if (fragment instanceof DialogFragment 447 && fragment.getTag() != null 448 && !fragment.getTag().equals(tag)) { 449 Log.d(TAG, "Remove staled opening dialog " + fragment.getTag()); 450 ((DialogFragment) fragment).dismissAllowingStateLoss(); 451 logDialogDismissEvent(fragment); 452 } 453 } 454 }); 455 } 456 457 /** Close opening dialogs for le audio device */ closeOpeningDialogsForLeaDevice(@onNull CachedBluetoothDevice cachedDevice)458 public void closeOpeningDialogsForLeaDevice(@NonNull CachedBluetoothDevice cachedDevice) { 459 if (mHostFragment == null) return; 460 int groupId = BluetoothUtils.getGroupId(cachedDevice); 461 AudioSharingUtils.postOnMainThread( 462 mContext, 463 () -> { 464 List<Fragment> fragments; 465 try { 466 fragments = mHostFragment.getChildFragmentManager().getFragments(); 467 } catch (IllegalStateException e) { 468 Log.d(TAG, "Fail to closeOpeningDialogsForLeaDevice: " + e.getMessage()); 469 return; 470 } 471 for (Fragment fragment : fragments) { 472 CachedBluetoothDevice device = getCachedBluetoothDeviceFromDialog(fragment); 473 if (device != null 474 && groupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID 475 && BluetoothUtils.getGroupId(device) == groupId) { 476 Log.d(TAG, "Remove staled opening dialog for group " + groupId); 477 ((DialogFragment) fragment).dismissAllowingStateLoss(); 478 logDialogDismissEvent(fragment); 479 } 480 } 481 }); 482 } 483 484 /** Close opening dialogs for non le audio device */ closeOpeningDialogsForNonLeaDevice(@onNull CachedBluetoothDevice cachedDevice)485 public void closeOpeningDialogsForNonLeaDevice(@NonNull CachedBluetoothDevice cachedDevice) { 486 if (mHostFragment == null) return; 487 String address = cachedDevice.getAddress(); 488 AudioSharingUtils.postOnMainThread( 489 mContext, 490 () -> { 491 List<Fragment> fragments; 492 try { 493 fragments = mHostFragment.getChildFragmentManager().getFragments(); 494 } catch (IllegalStateException e) { 495 Log.d(TAG, "Fail to closeOpeningDialogsForNonLeaDevice: " + e.getMessage()); 496 return; 497 } 498 for (Fragment fragment : fragments) { 499 CachedBluetoothDevice device = getCachedBluetoothDeviceFromDialog(fragment); 500 if (device != null && address != null && address.equals( 501 device.getAddress())) { 502 Log.d( 503 TAG, 504 "Remove staled opening dialog for device " 505 + cachedDevice.getDevice().getAnonymizedAddress()); 506 ((DialogFragment) fragment).dismissAllowingStateLoss(); 507 logDialogDismissEvent(fragment); 508 } 509 } 510 }); 511 } 512 513 @Nullable getCachedBluetoothDeviceFromDialog(Fragment fragment)514 private CachedBluetoothDevice getCachedBluetoothDeviceFromDialog(Fragment fragment) { 515 CachedBluetoothDevice device = null; 516 if (fragment instanceof AudioSharingJoinDialogFragment) { 517 device = ((AudioSharingJoinDialogFragment) fragment).getDevice(); 518 } else if (fragment instanceof AudioSharingStopDialogFragment) { 519 device = ((AudioSharingStopDialogFragment) fragment).getDevice(); 520 } else if (fragment instanceof AudioSharingDisconnectDialogFragment) { 521 device = ((AudioSharingDisconnectDialogFragment) fragment).getDevice(); 522 } 523 return device; 524 } 525 removeSourceForGroup( int groupId, Map<Integer, List<BluetoothDevice>> groupedDevices)526 private void removeSourceForGroup( 527 int groupId, Map<Integer, List<BluetoothDevice>> groupedDevices) { 528 if (mAssistant == null) { 529 Log.d(TAG, "Fail to remove source due to null profiles, group = " + groupId); 530 return; 531 } 532 if (!groupedDevices.containsKey(groupId)) { 533 Log.d(TAG, "Fail to remove source for group " + groupId); 534 return; 535 } 536 groupedDevices.getOrDefault(groupId, ImmutableList.of()).stream() 537 .forEach( 538 device -> { 539 for (BluetoothLeBroadcastReceiveState source : 540 mAssistant.getAllSources(device)) { 541 mAssistant.removeSource(device, source.getSourceId()); 542 } 543 }); 544 } 545 addSourceForGroup( int groupId, Map<Integer, List<BluetoothDevice>> groupedDevices)546 private void addSourceForGroup( 547 int groupId, Map<Integer, List<BluetoothDevice>> groupedDevices) { 548 if (mBroadcast == null || mAssistant == null) { 549 Log.d(TAG, "Fail to add source due to null profiles, group = " + groupId); 550 return; 551 } 552 if (!groupedDevices.containsKey(groupId)) { 553 Log.d(TAG, "Fail to add source due to invalid group id, group = " + groupId); 554 return; 555 } 556 groupedDevices.getOrDefault(groupId, ImmutableList.of()).stream() 557 .forEach( 558 device -> 559 mAssistant.addSource( 560 device, 561 mBroadcast.getLatestBluetoothLeBroadcastMetadata(), 562 /* isGroupOp= */ false)); 563 } 564 isBroadcasting()565 private boolean isBroadcasting() { 566 return mBroadcast != null && mBroadcast.isEnabled(null); 567 } 568 logDialogDismissEvent(Fragment fragment)569 private void logDialogDismissEvent(Fragment fragment) { 570 var unused = 571 ThreadUtils.postOnBackgroundThread( 572 () -> { 573 int pageId = SettingsEnums.PAGE_UNKNOWN; 574 if (fragment instanceof AudioSharingJoinDialogFragment) { 575 pageId = 576 ((AudioSharingJoinDialogFragment) fragment) 577 .getMetricsCategory(); 578 } else if (fragment instanceof AudioSharingStopDialogFragment) { 579 pageId = 580 ((AudioSharingStopDialogFragment) fragment) 581 .getMetricsCategory(); 582 } else if (fragment instanceof AudioSharingDisconnectDialogFragment) { 583 pageId = 584 ((AudioSharingDisconnectDialogFragment) fragment) 585 .getMetricsCategory(); 586 } 587 mMetricsFeatureProvider.action( 588 mContext, 589 SettingsEnums.ACTION_AUDIO_SHARING_DIALOG_AUTO_DISMISS, 590 pageId); 591 }); 592 } 593 } 594