Loading res/xml/bluetooth_audio_streams.xml +2 −1 Original line number Diff line number Diff line Loading @@ -34,6 +34,7 @@ <com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryPreference android:key="audio_streams_nearby_category" android:title="@string/audio_streams_pref_title" /> android:title="@string/audio_streams_pref_title" settings:controller="com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryController" /> </PreferenceScreen> No newline at end of file src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamPreference.java +19 −7 Original line number Diff line number Diff line Loading @@ -19,6 +19,8 @@ package com.android.settings.connecteddevice.audiosharing.audiostreams; import android.content.Context; import android.util.AttributeSet; import androidx.annotation.Nullable; import com.android.settings.R; import com.android.settingslib.widget.TwoTargetPreference; Loading @@ -27,25 +29,35 @@ import com.android.settingslib.widget.TwoTargetPreference; * {@link TwoTargetPreference}. */ public class AudioStreamPreference extends TwoTargetPreference { private boolean mShowLock = true; private boolean mIsConnected = false; /** * Sets whether to display the lock icon. * Update preference UI based on connection status * * @param showLock Should show / hide the lock icon * @param isConnected Is this streams connected */ public void setShowLock(boolean showLock) { mShowLock = showLock; public void setIsConnected( boolean isConnected, @Nullable OnPreferenceClickListener onPreferenceClickListener) { if (mIsConnected == isConnected && getOnPreferenceClickListener() == onPreferenceClickListener) { // Nothing to update. return; } mIsConnected = isConnected; setSummary(isConnected ? "Listening now" : ""); setOrder(isConnected ? 0 : 1); setOnPreferenceClickListener(onPreferenceClickListener); notifyChanged(); } public AudioStreamPreference(Context context, AttributeSet attrs) { public AudioStreamPreference(Context context, @Nullable AttributeSet attrs) { super(context, attrs); setIcon(R.drawable.ic_bt_audio_sharing); } @Override protected boolean shouldHideSecondTarget() { return !mShowLock; return mIsConnected; } @Override Loading src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsBroadcastAssistantCallback.java 0 → 100644 +144 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.connecteddevice.audiosharing.audiostreams; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothLeBroadcastAssistant; import android.bluetooth.BluetoothLeBroadcastMetadata; import android.bluetooth.BluetoothLeBroadcastReceiveState; import android.util.Log; import com.android.settingslib.bluetooth.BluetoothUtils; import java.util.Locale; public class AudioStreamsBroadcastAssistantCallback implements BluetoothLeBroadcastAssistant.Callback { private static final String TAG = "AudioStreamsBroadcastAssistantCallback"; private static final boolean DEBUG = BluetoothUtils.D; private AudioStreamsProgressCategoryController mCategoryController; public AudioStreamsBroadcastAssistantCallback( AudioStreamsProgressCategoryController audioStreamsProgressCategoryController) { mCategoryController = audioStreamsProgressCategoryController; } @Override public void onReceiveStateChanged( BluetoothDevice sink, int sourceId, BluetoothLeBroadcastReceiveState state) { if (DEBUG) { Log.d( TAG, "onReceiveStateChanged() sink : " + sink.getAddress() + " sourceId: " + sourceId + " state: " + state); } } @Override public void onSearchStartFailed(int reason) { Log.w(TAG, "onSearchStartFailed() reason : " + reason); mCategoryController.showToast( String.format(Locale.US, "Failed to start scanning, reason %d", reason)); } @Override public void onSearchStarted(int reason) { if (mCategoryController == null) { Log.w(TAG, "onSearchStarted() : mCategoryController is null!"); return; } if (DEBUG) { Log.d(TAG, "onSearchStarted() reason : " + reason); } mCategoryController.setScanning(true); } @Override public void onSearchStopFailed(int reason) { Log.w(TAG, "onSearchStopFailed() reason : " + reason); mCategoryController.showToast( String.format(Locale.US, "Failed to stop scanning, reason %d", reason)); } @Override public void onSearchStopped(int reason) { if (mCategoryController == null) { Log.w(TAG, "onSearchStopped() : mCategoryController is null!"); return; } if (DEBUG) { Log.d(TAG, "onSearchStopped() reason : " + reason); } mCategoryController.setScanning(false); } @Override public void onSourceAddFailed( BluetoothDevice sink, BluetoothLeBroadcastMetadata source, int reason) {} @Override public void onSourceAdded(BluetoothDevice sink, int sourceId, int reason) { if (DEBUG) { Log.d( TAG, "onSourceAdded() sink : " + sink.getAddress() + " sourceId: " + sourceId + " reason: " + reason); } } @Override public void onSourceFound(BluetoothLeBroadcastMetadata source) { if (mCategoryController == null) { Log.w(TAG, "onSourceFound() : mCategoryController is null!"); return; } if (DEBUG) { Log.d(TAG, "onSourceFound() broadcastId : " + source.getBroadcastId()); } mCategoryController.addSourceFound(source); } @Override public void onSourceLost(int broadcastId) { if (DEBUG) { Log.d(TAG, "onSourceLost() broadcastId : " + broadcastId); } mCategoryController.removeSourceLost(broadcastId); } @Override public void onSourceModified(BluetoothDevice sink, int sourceId, int reason) {} @Override public void onSourceModifyFailed(BluetoothDevice sink, int sourceId, int reason) {} @Override public void onSourceRemoveFailed(BluetoothDevice sink, int sourceId, int reason) {} @Override public void onSourceRemoved(BluetoothDevice sink, int sourceId, int reason) {} } src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java 0 → 100644 +265 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.connecteddevice.audiosharing.audiostreams; import static java.util.Collections.emptyList; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothLeAudioContentMetadata; import android.bluetooth.BluetoothLeBroadcastMetadata; import android.bluetooth.BluetoothLeBroadcastReceiveState; import android.content.Context; import android.util.Log; import androidx.annotation.NonNull; import androidx.lifecycle.DefaultLifecycleObserver; import androidx.lifecycle.LifecycleOwner; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; import com.android.settings.bluetooth.Utils; import com.android.settings.connecteddevice.audiosharing.AudioSharingUtils; import com.android.settings.core.BasePreferenceController; import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; import com.android.settingslib.utils.ThreadUtils; import com.google.common.base.Strings; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.stream.Stream; import javax.annotation.Nullable; public class AudioStreamsProgressCategoryController extends BasePreferenceController implements DefaultLifecycleObserver { private static final String TAG = "AudioStreamsProgressCategoryController"; private static final boolean DEBUG = BluetoothUtils.D; private final Executor mExecutor; private final AudioStreamsBroadcastAssistantCallback mBroadcastAssistantCallback; private final LocalBluetoothManager mBluetoothManager; private final @Nullable LocalBluetoothLeBroadcastAssistant mLeBroadcastAssistant; private final ConcurrentHashMap<Integer, AudioStreamPreference> mBroadcastIdToPreferenceMap = new ConcurrentHashMap<>(); private @Nullable AudioStreamsProgressCategoryPreference mCategoryPreference; public AudioStreamsProgressCategoryController(Context context, String preferenceKey) { super(context, preferenceKey); mExecutor = Executors.newSingleThreadExecutor(); mBluetoothManager = Utils.getLocalBtManager(mContext); mLeBroadcastAssistant = getLeBroadcastAssistant(mBluetoothManager); mBroadcastAssistantCallback = new AudioStreamsBroadcastAssistantCallback(this); } @Override public int getAvailabilityStatus() { return AVAILABLE; } @Override public void displayPreference(PreferenceScreen screen) { super.displayPreference(screen); mCategoryPreference = screen.findPreference(getPreferenceKey()); } @Override public void onStart(@NonNull LifecycleOwner owner) { if (mLeBroadcastAssistant == null) { Log.w(TAG, "onStart(): LeBroadcastAssistant is null!"); return; } mBroadcastIdToPreferenceMap.clear(); if (mCategoryPreference != null) { mCategoryPreference.removeAll(); } mExecutor.execute( () -> { mLeBroadcastAssistant.registerServiceCallBack( mExecutor, mBroadcastAssistantCallback); if (DEBUG) { Log.d(TAG, "scanAudioStreamsStart()"); } mLeBroadcastAssistant.startSearchingForSources(emptyList()); // Display currently connected streams var unused = ThreadUtils.postOnBackgroundThread( () -> { for (var sink : getActiveSinksOnAssistant(mBluetoothManager)) { mLeBroadcastAssistant .getAllSources(sink) .forEach(this::addSourceConnected); } }); }); } @Override public void onStop(@NonNull LifecycleOwner owner) { if (mLeBroadcastAssistant == null) { Log.w(TAG, "onStop(): LeBroadcastAssistant is null!"); return; } mExecutor.execute( () -> { if (mLeBroadcastAssistant.isSearchInProgress()) { if (DEBUG) { Log.d(TAG, "scanAudioStreamsStop()"); } mLeBroadcastAssistant.stopSearchingForSources(); } mLeBroadcastAssistant.unregisterServiceCallBack(mBroadcastAssistantCallback); }); } void setScanning(boolean isScanning) { ThreadUtils.postOnMainThread( () -> { if (mCategoryPreference != null) mCategoryPreference.setProgress(isScanning); }); } void addSourceFound(BluetoothLeBroadcastMetadata source) { Preference.OnPreferenceClickListener onClickListener = preference -> { if (DEBUG) { Log.d(TAG, "preferenceClicked(): attempt to join broadcast"); } // TODO(chelseahao): add source to sink return true; }; mBroadcastIdToPreferenceMap.computeIfAbsent( source.getBroadcastId(), k -> { var p = createPreference(source, onClickListener); ThreadUtils.postOnMainThread( () -> { if (mCategoryPreference != null) { mCategoryPreference.addPreference(p); } }); return p; }); } void removeSourceLost(int broadcastId) { var toRemove = mBroadcastIdToPreferenceMap.remove(broadcastId); if (toRemove != null) { ThreadUtils.postOnMainThread( () -> { if (mCategoryPreference != null) { mCategoryPreference.removePreference(toRemove); } }); } // TODO(chelseahao): remove source from sink } private void addSourceConnected(BluetoothLeBroadcastReceiveState state) { mBroadcastIdToPreferenceMap.compute( state.getBroadcastId(), (k, v) -> { if (v == null) { // Create a new preference as the source has not been added. var p = createPreference(state); ThreadUtils.postOnMainThread( () -> { if (mCategoryPreference != null) { mCategoryPreference.addPreference(p); } }); return p; } else { // This source has been added either by scanning, or it's currently // connected to another active sink. Update its connection status to true // if needed. ThreadUtils.postOnMainThread(() -> v.setIsConnected(true, null)); return v; } }); } private AudioStreamPreference createPreference( BluetoothLeBroadcastMetadata source, Preference.OnPreferenceClickListener onPreferenceClickListener) { AudioStreamPreference preference = new AudioStreamPreference(mContext, /* attrs= */ null); preference.setTitle( source.getSubgroups().stream() .map(s -> s.getContentMetadata().getProgramInfo()) .filter(i -> !Strings.isNullOrEmpty(i)) .findFirst() .orElse("Broadcast Id: " + source.getBroadcastId())); preference.setIsConnected(false, onPreferenceClickListener); return preference; } private AudioStreamPreference createPreference(BluetoothLeBroadcastReceiveState state) { AudioStreamPreference preference = new AudioStreamPreference(mContext, /* attrs= */ null); preference.setTitle( state.getSubgroupMetadata().stream() .map(BluetoothLeAudioContentMetadata::getProgramInfo) .filter(i -> !Strings.isNullOrEmpty(i)) .findFirst() .orElse("Broadcast Id: " + state.getBroadcastId())); preference.setIsConnected(true, null); return preference; } private static List<BluetoothDevice> getActiveSinksOnAssistant(LocalBluetoothManager manager) { if (manager == null) { Log.w(TAG, "getActiveSinksOnAssistant(): LocalBluetoothManager is null!"); return emptyList(); } return AudioSharingUtils.getActiveSinkOnAssistant(manager) .map( cachedBluetoothDevice -> Stream.concat( Stream.of(cachedBluetoothDevice.getDevice()), cachedBluetoothDevice.getMemberDevice().stream() .map(CachedBluetoothDevice::getDevice)) .toList()) .orElse(emptyList()); } private static @Nullable LocalBluetoothLeBroadcastAssistant getLeBroadcastAssistant( LocalBluetoothManager manager) { if (manager == null) { Log.w(TAG, "getLeBroadcastAssistant(): LocalBluetoothManager is null!"); return null; } LocalBluetoothProfileManager profileManager = manager.getProfileManager(); if (profileManager == null) { Log.w(TAG, "getLeBroadcastAssistant(): LocalBluetoothProfileManager is null!"); return null; } return profileManager.getLeAudioBroadcastAssistantProfile(); } void showToast(String msg) { AudioSharingUtils.toastMessage(mContext, msg); } } Loading
res/xml/bluetooth_audio_streams.xml +2 −1 Original line number Diff line number Diff line Loading @@ -34,6 +34,7 @@ <com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryPreference android:key="audio_streams_nearby_category" android:title="@string/audio_streams_pref_title" /> android:title="@string/audio_streams_pref_title" settings:controller="com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryController" /> </PreferenceScreen> No newline at end of file
src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamPreference.java +19 −7 Original line number Diff line number Diff line Loading @@ -19,6 +19,8 @@ package com.android.settings.connecteddevice.audiosharing.audiostreams; import android.content.Context; import android.util.AttributeSet; import androidx.annotation.Nullable; import com.android.settings.R; import com.android.settingslib.widget.TwoTargetPreference; Loading @@ -27,25 +29,35 @@ import com.android.settingslib.widget.TwoTargetPreference; * {@link TwoTargetPreference}. */ public class AudioStreamPreference extends TwoTargetPreference { private boolean mShowLock = true; private boolean mIsConnected = false; /** * Sets whether to display the lock icon. * Update preference UI based on connection status * * @param showLock Should show / hide the lock icon * @param isConnected Is this streams connected */ public void setShowLock(boolean showLock) { mShowLock = showLock; public void setIsConnected( boolean isConnected, @Nullable OnPreferenceClickListener onPreferenceClickListener) { if (mIsConnected == isConnected && getOnPreferenceClickListener() == onPreferenceClickListener) { // Nothing to update. return; } mIsConnected = isConnected; setSummary(isConnected ? "Listening now" : ""); setOrder(isConnected ? 0 : 1); setOnPreferenceClickListener(onPreferenceClickListener); notifyChanged(); } public AudioStreamPreference(Context context, AttributeSet attrs) { public AudioStreamPreference(Context context, @Nullable AttributeSet attrs) { super(context, attrs); setIcon(R.drawable.ic_bt_audio_sharing); } @Override protected boolean shouldHideSecondTarget() { return !mShowLock; return mIsConnected; } @Override Loading
src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsBroadcastAssistantCallback.java 0 → 100644 +144 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.connecteddevice.audiosharing.audiostreams; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothLeBroadcastAssistant; import android.bluetooth.BluetoothLeBroadcastMetadata; import android.bluetooth.BluetoothLeBroadcastReceiveState; import android.util.Log; import com.android.settingslib.bluetooth.BluetoothUtils; import java.util.Locale; public class AudioStreamsBroadcastAssistantCallback implements BluetoothLeBroadcastAssistant.Callback { private static final String TAG = "AudioStreamsBroadcastAssistantCallback"; private static final boolean DEBUG = BluetoothUtils.D; private AudioStreamsProgressCategoryController mCategoryController; public AudioStreamsBroadcastAssistantCallback( AudioStreamsProgressCategoryController audioStreamsProgressCategoryController) { mCategoryController = audioStreamsProgressCategoryController; } @Override public void onReceiveStateChanged( BluetoothDevice sink, int sourceId, BluetoothLeBroadcastReceiveState state) { if (DEBUG) { Log.d( TAG, "onReceiveStateChanged() sink : " + sink.getAddress() + " sourceId: " + sourceId + " state: " + state); } } @Override public void onSearchStartFailed(int reason) { Log.w(TAG, "onSearchStartFailed() reason : " + reason); mCategoryController.showToast( String.format(Locale.US, "Failed to start scanning, reason %d", reason)); } @Override public void onSearchStarted(int reason) { if (mCategoryController == null) { Log.w(TAG, "onSearchStarted() : mCategoryController is null!"); return; } if (DEBUG) { Log.d(TAG, "onSearchStarted() reason : " + reason); } mCategoryController.setScanning(true); } @Override public void onSearchStopFailed(int reason) { Log.w(TAG, "onSearchStopFailed() reason : " + reason); mCategoryController.showToast( String.format(Locale.US, "Failed to stop scanning, reason %d", reason)); } @Override public void onSearchStopped(int reason) { if (mCategoryController == null) { Log.w(TAG, "onSearchStopped() : mCategoryController is null!"); return; } if (DEBUG) { Log.d(TAG, "onSearchStopped() reason : " + reason); } mCategoryController.setScanning(false); } @Override public void onSourceAddFailed( BluetoothDevice sink, BluetoothLeBroadcastMetadata source, int reason) {} @Override public void onSourceAdded(BluetoothDevice sink, int sourceId, int reason) { if (DEBUG) { Log.d( TAG, "onSourceAdded() sink : " + sink.getAddress() + " sourceId: " + sourceId + " reason: " + reason); } } @Override public void onSourceFound(BluetoothLeBroadcastMetadata source) { if (mCategoryController == null) { Log.w(TAG, "onSourceFound() : mCategoryController is null!"); return; } if (DEBUG) { Log.d(TAG, "onSourceFound() broadcastId : " + source.getBroadcastId()); } mCategoryController.addSourceFound(source); } @Override public void onSourceLost(int broadcastId) { if (DEBUG) { Log.d(TAG, "onSourceLost() broadcastId : " + broadcastId); } mCategoryController.removeSourceLost(broadcastId); } @Override public void onSourceModified(BluetoothDevice sink, int sourceId, int reason) {} @Override public void onSourceModifyFailed(BluetoothDevice sink, int sourceId, int reason) {} @Override public void onSourceRemoveFailed(BluetoothDevice sink, int sourceId, int reason) {} @Override public void onSourceRemoved(BluetoothDevice sink, int sourceId, int reason) {} }
src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java 0 → 100644 +265 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.connecteddevice.audiosharing.audiostreams; import static java.util.Collections.emptyList; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothLeAudioContentMetadata; import android.bluetooth.BluetoothLeBroadcastMetadata; import android.bluetooth.BluetoothLeBroadcastReceiveState; import android.content.Context; import android.util.Log; import androidx.annotation.NonNull; import androidx.lifecycle.DefaultLifecycleObserver; import androidx.lifecycle.LifecycleOwner; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; import com.android.settings.bluetooth.Utils; import com.android.settings.connecteddevice.audiosharing.AudioSharingUtils; import com.android.settings.core.BasePreferenceController; import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; import com.android.settingslib.utils.ThreadUtils; import com.google.common.base.Strings; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.stream.Stream; import javax.annotation.Nullable; public class AudioStreamsProgressCategoryController extends BasePreferenceController implements DefaultLifecycleObserver { private static final String TAG = "AudioStreamsProgressCategoryController"; private static final boolean DEBUG = BluetoothUtils.D; private final Executor mExecutor; private final AudioStreamsBroadcastAssistantCallback mBroadcastAssistantCallback; private final LocalBluetoothManager mBluetoothManager; private final @Nullable LocalBluetoothLeBroadcastAssistant mLeBroadcastAssistant; private final ConcurrentHashMap<Integer, AudioStreamPreference> mBroadcastIdToPreferenceMap = new ConcurrentHashMap<>(); private @Nullable AudioStreamsProgressCategoryPreference mCategoryPreference; public AudioStreamsProgressCategoryController(Context context, String preferenceKey) { super(context, preferenceKey); mExecutor = Executors.newSingleThreadExecutor(); mBluetoothManager = Utils.getLocalBtManager(mContext); mLeBroadcastAssistant = getLeBroadcastAssistant(mBluetoothManager); mBroadcastAssistantCallback = new AudioStreamsBroadcastAssistantCallback(this); } @Override public int getAvailabilityStatus() { return AVAILABLE; } @Override public void displayPreference(PreferenceScreen screen) { super.displayPreference(screen); mCategoryPreference = screen.findPreference(getPreferenceKey()); } @Override public void onStart(@NonNull LifecycleOwner owner) { if (mLeBroadcastAssistant == null) { Log.w(TAG, "onStart(): LeBroadcastAssistant is null!"); return; } mBroadcastIdToPreferenceMap.clear(); if (mCategoryPreference != null) { mCategoryPreference.removeAll(); } mExecutor.execute( () -> { mLeBroadcastAssistant.registerServiceCallBack( mExecutor, mBroadcastAssistantCallback); if (DEBUG) { Log.d(TAG, "scanAudioStreamsStart()"); } mLeBroadcastAssistant.startSearchingForSources(emptyList()); // Display currently connected streams var unused = ThreadUtils.postOnBackgroundThread( () -> { for (var sink : getActiveSinksOnAssistant(mBluetoothManager)) { mLeBroadcastAssistant .getAllSources(sink) .forEach(this::addSourceConnected); } }); }); } @Override public void onStop(@NonNull LifecycleOwner owner) { if (mLeBroadcastAssistant == null) { Log.w(TAG, "onStop(): LeBroadcastAssistant is null!"); return; } mExecutor.execute( () -> { if (mLeBroadcastAssistant.isSearchInProgress()) { if (DEBUG) { Log.d(TAG, "scanAudioStreamsStop()"); } mLeBroadcastAssistant.stopSearchingForSources(); } mLeBroadcastAssistant.unregisterServiceCallBack(mBroadcastAssistantCallback); }); } void setScanning(boolean isScanning) { ThreadUtils.postOnMainThread( () -> { if (mCategoryPreference != null) mCategoryPreference.setProgress(isScanning); }); } void addSourceFound(BluetoothLeBroadcastMetadata source) { Preference.OnPreferenceClickListener onClickListener = preference -> { if (DEBUG) { Log.d(TAG, "preferenceClicked(): attempt to join broadcast"); } // TODO(chelseahao): add source to sink return true; }; mBroadcastIdToPreferenceMap.computeIfAbsent( source.getBroadcastId(), k -> { var p = createPreference(source, onClickListener); ThreadUtils.postOnMainThread( () -> { if (mCategoryPreference != null) { mCategoryPreference.addPreference(p); } }); return p; }); } void removeSourceLost(int broadcastId) { var toRemove = mBroadcastIdToPreferenceMap.remove(broadcastId); if (toRemove != null) { ThreadUtils.postOnMainThread( () -> { if (mCategoryPreference != null) { mCategoryPreference.removePreference(toRemove); } }); } // TODO(chelseahao): remove source from sink } private void addSourceConnected(BluetoothLeBroadcastReceiveState state) { mBroadcastIdToPreferenceMap.compute( state.getBroadcastId(), (k, v) -> { if (v == null) { // Create a new preference as the source has not been added. var p = createPreference(state); ThreadUtils.postOnMainThread( () -> { if (mCategoryPreference != null) { mCategoryPreference.addPreference(p); } }); return p; } else { // This source has been added either by scanning, or it's currently // connected to another active sink. Update its connection status to true // if needed. ThreadUtils.postOnMainThread(() -> v.setIsConnected(true, null)); return v; } }); } private AudioStreamPreference createPreference( BluetoothLeBroadcastMetadata source, Preference.OnPreferenceClickListener onPreferenceClickListener) { AudioStreamPreference preference = new AudioStreamPreference(mContext, /* attrs= */ null); preference.setTitle( source.getSubgroups().stream() .map(s -> s.getContentMetadata().getProgramInfo()) .filter(i -> !Strings.isNullOrEmpty(i)) .findFirst() .orElse("Broadcast Id: " + source.getBroadcastId())); preference.setIsConnected(false, onPreferenceClickListener); return preference; } private AudioStreamPreference createPreference(BluetoothLeBroadcastReceiveState state) { AudioStreamPreference preference = new AudioStreamPreference(mContext, /* attrs= */ null); preference.setTitle( state.getSubgroupMetadata().stream() .map(BluetoothLeAudioContentMetadata::getProgramInfo) .filter(i -> !Strings.isNullOrEmpty(i)) .findFirst() .orElse("Broadcast Id: " + state.getBroadcastId())); preference.setIsConnected(true, null); return preference; } private static List<BluetoothDevice> getActiveSinksOnAssistant(LocalBluetoothManager manager) { if (manager == null) { Log.w(TAG, "getActiveSinksOnAssistant(): LocalBluetoothManager is null!"); return emptyList(); } return AudioSharingUtils.getActiveSinkOnAssistant(manager) .map( cachedBluetoothDevice -> Stream.concat( Stream.of(cachedBluetoothDevice.getDevice()), cachedBluetoothDevice.getMemberDevice().stream() .map(CachedBluetoothDevice::getDevice)) .toList()) .orElse(emptyList()); } private static @Nullable LocalBluetoothLeBroadcastAssistant getLeBroadcastAssistant( LocalBluetoothManager manager) { if (manager == null) { Log.w(TAG, "getLeBroadcastAssistant(): LocalBluetoothManager is null!"); return null; } LocalBluetoothProfileManager profileManager = manager.getProfileManager(); if (profileManager == null) { Log.w(TAG, "getLeBroadcastAssistant(): LocalBluetoothProfileManager is null!"); return null; } return profileManager.getLeAudioBroadcastAssistantProfile(); } void showToast(String msg) { AudioSharingUtils.toastMessage(mContext, msg); } }