• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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