Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 15c83448 authored by Jakub Tyszkowski's avatar Jakub Tyszkowski Committed by Hyundo Moon
Browse files

Rebase LE audio test app with new APIs

This CL replaces the usages of legacy broadcast scan code
with the new Broadcast Scan Assistant APIs.

Bug: 224800279
Test: make LeAudioApp -j;
Change-Id: Ib13e9e61ac4fdba31102889aa0efd4a0717cff54
parent cfc4b2f5
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -36,6 +36,11 @@
            android:excludeFromRecents="true"
            android:theme="@style/AppTheme.NoActionBar">
        </activity>
        <activity
            android:name=".BroadcastScanActivity"
            android:excludeFromRecents="true"
            android:theme="@style/AppTheme.NoActionBar">
        </activity>
    </application>

</manifest>
+302 −5
Original line number Diff line number Diff line
@@ -26,40 +26,45 @@ import android.content.IntentFilter;
import android.os.ParcelUuid;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.core.util.Pair;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;

import com.android.bluetooth.leaudio.R;

public class BluetoothProxy {
    private static BluetoothProxy INSTANCE;
    private final Application application;
    private final BluetoothAdapter bluetoothAdapter;
    private BluetoothLeAudio bluetoothLeAudio = null;
    private BluetoothLeBroadcast mBluetoothLeBroadcast = null;
    private BluetoothLeBroadcastAssistant mBluetoothLeBroadcastAssistant = null;
    private Set<BluetoothDevice> mBroadcastScanOnBehalfDevices = new HashSet<>();
    private BluetoothCsipSetCoordinator bluetoothCsis = null;
    private BluetoothVolumeControl bluetoothVolumeControl = null;
    private BluetoothHapClient bluetoothHapClient = null;
    private BluetoothProfile.ServiceListener profileListener = null;
    private BluetoothHapClient.Callback hapCallback = null;
    private OnBassEventListener mBassEventListener;
    private OnLocalBroadcastEventListener mLocalBroadcastEventListener;
    private final IntentFilter adapterIntentFilter;
    private final IntentFilter bassIntentFilter;
    private IntentFilter intentFilter;
    private final ExecutorService mExecutor;

@@ -290,6 +295,9 @@ public class BluetoothProxy {
                    }

                    mBroadcastAddedMutableLive.postValue(broadcastId);
                    if (mLocalBroadcastEventListener != null) {
                        mLocalBroadcastEventListener.onBroadcastStarted(broadcastId);
                    }
                }

                @Override
@@ -301,6 +309,9 @@ public class BluetoothProxy {
                @Override
                public void onBroadcastStopped(int reason, int broadcastId) {
                    mBroadcastRemovedMutableLive.postValue(new Pair<>(reason, broadcastId));
                    if (mLocalBroadcastEventListener != null) {
                        mLocalBroadcastEventListener.onBroadcastStopped(broadcastId);
                    }
                }

                @Override
@@ -323,6 +334,9 @@ public class BluetoothProxy {
                public void onBroadcastUpdated(int reason, int broadcastId) {
                    mBroadcastStatusMutableLive.postValue("Broadcast " + broadcastId
                            + "has been updated due to reason: " + reason);
                    if (mLocalBroadcastEventListener != null) {
                        mLocalBroadcastEventListener.onBroadcastUpdated(broadcastId);
                    }
                }

                @Override
@@ -335,6 +349,144 @@ public class BluetoothProxy {
                public void onBroadcastMetadataChanged(int broadcastId,
                        BluetoothLeBroadcastMetadata metadata) {
                    mBroadcastUpdateMutableLive.postValue(metadata);
                    if (mLocalBroadcastEventListener != null) {
                        mLocalBroadcastEventListener.onBroadcastMetadataChanged(
                                broadcastId, metadata);
                    }
                }
            };

    // TODO: Add behaviors in empty methods if necessary.
    private final BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback =
            new BluetoothLeBroadcastAssistant.Callback() {
        @Override
        public void onSearchStarted(int reason) {}

        @Override
        public void onSearchStartFailed(int reason) {}

        @Override
        public void onSearchStopped(int reason) {}

        @Override
        public void onSearchStopFailed(int reason) {}

        @Override
        public void onSourceFound(BluetoothLeBroadcastMetadata source) {
            if (mBassEventListener != null) {
                mBassEventListener.onSourceFound(source);
            }
        }

        @Override
        public void onSourceAdded(BluetoothDevice sink, int sourceId, int reason) {}

        @Override
        public void onSourceAddFailed(BluetoothDevice sink,
                BluetoothLeBroadcastMetadata source, int reason) {}

        @Override
        public void onSourceModified(BluetoothDevice sink, int sourceId, int reason) {}

        @Override
        public void onSourceModifyFailed(BluetoothDevice sink, int sourceId, int reason) {}

        @Override
        public void onSourceRemoved(BluetoothDevice sink, int sourceId, int reason) {}

        @Override
        public void onSourceRemoveFailed(BluetoothDevice sink, int sourceId, int reason) {}

        @Override
        public void onReceiveStateChanged(BluetoothDevice sink, int sourceId,
                BluetoothLeBroadcastReceiveState state) {
            if (allLeAudioDevicesMutable.getValue() != null) {
                Optional<LeAudioDeviceStateWrapper> valid_device_opt = allLeAudioDevicesMutable
                        .getValue().stream()
                        .filter(stateWrapper -> stateWrapper.device.getAddress().equals(
                                sink.getAddress()))
                        .findAny();

                if (!valid_device_opt.isPresent())
                    return;

                LeAudioDeviceStateWrapper valid_device = valid_device_opt.get();
                LeAudioDeviceStateWrapper.BassData svc_data = valid_device.bassData;

                // TODO: Is the receiver_id same with BluetoothLeBroadcastReceiveState.getSourceId()?
                //       If not, find getSourceId() usages and fix the issues.
//                rstate.receiver_id = intent.getIntExtra(
//                        BluetoothBroadcastAudioScan.EXTRA_BASS_RECEIVER_ID, -1);
                /**
                 * From "Introducing-Bluetooth-LE-Audio-book" 8.6.3.1:
                 *
                 * The Source_ID is an Acceptor generated number which is used to identify a
                 * specific set of
                 * broadcast device and BIG information. It is local to an Acceptor and used as a
                 * reference for
                 * a Broadcast Assistant. In the case of a Coordinated Set of Acceptors, such as
                 * a left and right
                 * earbud, the Source_IDs are not related and may be different, even if both are
                 * receiving the
                 * same BIS, as each Acceptor independently creates their own Source ID values
                 */

                /**
                 * From BluetoothBroadcastAudioScan.EXTRA_BASS_RECEIVER_ID:
                 *
                 * Broadcast receiver's endpoint identifier.
                 */

                HashMap<Integer, BluetoothLeBroadcastReceiveState> states =
                        svc_data.receiverStatesMutable.getValue();
                if (states == null)
                    states = new HashMap<>();
                states.put(state.getSourceId(), state);

                // Use SetValue instead of PostValue() since we want to make it
                // synchronous due to getValue() we do here as well
                // Otherwise we could miss the update and store only the last
                // receiver ID
                svc_data.receiverStatesMutable.setValue(states);
            }
        }
    };

    private final BroadcastReceiver bassIntentReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (action.equals(BluetoothLeBroadcastAssistant.ACTION_CONNECTION_STATE_CHANGED)) {
                final BluetoothDevice device = intent.getParcelableExtra(
                        BluetoothDevice.EXTRA_DEVICE, BluetoothDevice.class);

                if (allLeAudioDevicesMutable.getValue() != null) {
                    if (device != null) {
                        Optional<LeAudioDeviceStateWrapper> valid_device_opt =
                                allLeAudioDevicesMutable
                                        .getValue().stream()
                                        .filter(state -> state.device.getAddress().equals(
                                                device.getAddress()))
                                        .findAny();

                        if (valid_device_opt.isPresent()) {
                            LeAudioDeviceStateWrapper valid_device = valid_device_opt.get();
                            LeAudioDeviceStateWrapper.BassData svc_data = valid_device.bassData;

                            final int toState = intent
                                    .getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
                            if (toState == BluetoothProfile.STATE_CONNECTED
                                    || toState == BluetoothProfile.STATE_DISCONNECTED)
                                svc_data.isConnectedMutable.postValue(
                                        toState == BluetoothProfile.STATE_CONNECTED);
                        }
                    }
                }
            }
            // TODO: Remove this if unnecessary.
//          case BluetoothBroadcastAudioScan.ACTION_BASS_BROADCAST_ANNONCEMENT_AVAILABLE:
//              // FIXME: Never happen since there is no valid device with this intent
//              break;
        }
    };

@@ -360,6 +512,10 @@ public class BluetoothProxy {
        adapterIntentFilter = new IntentFilter();
        adapterIntentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
        application.registerReceiver(adapterIntentReceiver, adapterIntentFilter);

        bassIntentFilter = new IntentFilter();
        bassIntentFilter.addAction(BluetoothLeBroadcastAssistant.ACTION_CONNECTION_STATE_CHANGED);
        application.registerReceiver(bassIntentReceiver, bassIntentFilter);
    }

    // Lazy constructing Singleton acquire method
@@ -512,6 +668,12 @@ public class BluetoothProxy {
                        mBluetoothLeBroadcast = (BluetoothLeBroadcast) bluetoothProfile;
                        mBluetoothLeBroadcast.registerCallback(mExecutor, mBroadcasterCallback);
                        break;
                    case BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT:
                        mBluetoothLeBroadcastAssistant = (BluetoothLeBroadcastAssistant)
                                bluetoothProfile;
                        mBluetoothLeBroadcastAssistant.registerCallback(mExecutor,
                                mBroadcastAssistantCallback);
                        break;
                }
                queryLeAudioDevices();
            }
@@ -525,6 +687,7 @@ public class BluetoothProxy {
        initVolumeControlProxy();
        initHapProxy();
        initLeAudioBroadcastProxy();
        initBassProxy();
    }

    public void cleanupProfiles() {
@@ -535,6 +698,7 @@ public class BluetoothProxy {
        cleanupVolumeControlProxy();
        cleanupHapProxy();
        cleanupLeAudioBroadcastProxy();
        cleanupBassProxy();

        profileListener = null;
    }
@@ -605,6 +769,19 @@ public class BluetoothProxy {
        }
    }

    private void initBassProxy() {
        bluetoothAdapter.getProfileProxy(this.application, profileListener,
                BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
    }

    private void cleanupBassProxy() {
        if (mBluetoothLeBroadcastAssistant != null) {
            mBluetoothLeBroadcastAssistant.unregisterCallback(mBroadcastAssistantCallback);
            bluetoothAdapter.closeProfileProxy(BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT,
                    mBluetoothLeBroadcastAssistant);
        }
    }

    private Boolean checkForEnabledBluetooth() {
        Boolean current_state = bluetoothAdapter.isEnabled();

@@ -703,6 +880,21 @@ public class BluetoothProxy {
                    }
                }

                if (Arrays.asList(dev.getUuids() != null ? dev.getUuids() : new ParcelUuid[0])
                        .contains(ParcelUuid.fromString(
                                application.getString(R.string.svc_uuid_broadcast_audio)))) {
                    if (state_wrapper.bassData == null)
                        state_wrapper.bassData = new LeAudioDeviceStateWrapper.BassData();
                    valid_device = true;

                    if (mBluetoothLeBroadcastAssistant != null) {
                        boolean is_connected = mBluetoothLeBroadcastAssistant
                                .getConnectionState(dev) == BluetoothProfile.STATE_CONNECTED;
                        state_wrapper.bassData.isConnectedMutable.setValue(is_connected);
                    }
                }


                if (valid_device) validDevices.add(state_wrapper);
            }

@@ -822,6 +1014,88 @@ public class BluetoothProxy {
        }
    }

    public void connectBass(BluetoothDevice device, boolean connect) {
        if (mBluetoothLeBroadcastAssistant != null) {
            if (connect) {
                mBluetoothLeBroadcastAssistant.setConnectionPolicy(device,
                        BluetoothProfile.CONNECTION_POLICY_ALLOWED);
            } else {
                mBluetoothLeBroadcastAssistant.setConnectionPolicy(device,
                        BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
            }
        }
    }

    public boolean scanForBroadcasts(@NonNull BluetoothDevice onBehalfDevice, boolean scan) {
        if (mBluetoothLeBroadcastAssistant != null) {
            // Note: startSearchingForSources() does not support scanning on behalf of
            // a specific device - it only searches for all BASS connected devices.
            // Therefore, we manage the list of the devices and start/stop the scanning.
            if (scan) {
                mBroadcastScanOnBehalfDevices.add(onBehalfDevice);
                mBluetoothLeBroadcastAssistant.startSearchingForSources(new ArrayList<>());
                if (mBassEventListener != null) {
                    mBassEventListener.onScanningStateChanged(true);
                }
            } else {
                mBroadcastScanOnBehalfDevices.remove(onBehalfDevice);
                if (mBroadcastScanOnBehalfDevices.isEmpty()) {
                    mBluetoothLeBroadcastAssistant.stopSearchingForSources();
                    if (mBassEventListener != null) {
                        mBassEventListener.onScanningStateChanged(false);
                    }
                }
            }
            return true;
        }
        return false;
    }

    public boolean stopBroadcastObserving() {
        if (mBluetoothLeBroadcastAssistant != null) {
            mBroadcastScanOnBehalfDevices.clear();
            mBluetoothLeBroadcastAssistant.stopSearchingForSources();
            if (mBassEventListener != null) {
                mBassEventListener.onScanningStateChanged(false);
            }
            return true;
        }
        return false;
    }

    // TODO: Uncomment this method if necessary
//    public boolean getBroadcastReceiverState(BluetoothDevice device, int receiver_id) {
//        if (mBluetoothLeBroadcastAssistant != null) {
//            return mBluetoothLeBroadcastAssistant.getBroadcastReceiverState(device, receiver_id);
//        }
//        return false;
//    }

    public boolean addBroadcastSource(BluetoothDevice sink, BluetoothLeBroadcastMetadata sourceMetadata) {
        if (mBluetoothLeBroadcastAssistant != null) {
            mBluetoothLeBroadcastAssistant.addSource(sink, sourceMetadata, true /* isGroupOp */);
            return true;
        }
        return false;
    }

    public boolean modifyBroadcastSource(BluetoothDevice sink, int sourceId,
            BluetoothLeBroadcastMetadata metadata) {
        if (mBluetoothLeBroadcastAssistant != null) {
            mBluetoothLeBroadcastAssistant.modifySource(sink, sourceId, metadata);
            return true;
        }
        return false;
    }

    public boolean removeBroadcastSource(BluetoothDevice sink, int sourceId) {
        if (mBluetoothLeBroadcastAssistant != null) {
            mBluetoothLeBroadcastAssistant.removeSource(sink, sourceId);
            return true;
        }
        return false;
    }

    public void setVolume(BluetoothDevice device, int volume) {
        if (bluetoothLeAudio != null) {
            bluetoothLeAudio.setVolume(volume);
@@ -843,7 +1117,7 @@ public class BluetoothProxy {
                        BluetoothProfile.CONNECTION_POLICY_ALLOWED);
            } else {
                bluetoothHapClient.setConnectionPolicy(device,
                        BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
                        BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
            }
        }
    }
@@ -1056,7 +1330,7 @@ public class BluetoothProxy {
        return true;
    }

    public List<BluetoothLeBroadcastMetadata> getAllBroadcastMetadata() {
    public List<BluetoothLeBroadcastMetadata> getAllLocalBroadcasts() {
        if (mBluetoothLeBroadcast == null) return Collections.emptyList();
        return mBluetoothLeBroadcast.getAllBroadcastMetadata();
    }
@@ -1086,4 +1360,27 @@ public class BluetoothProxy {
        return (bluetoothAdapter
                .isLeAudioBroadcastSourceSupported() == BluetoothStatusCodes.FEATURE_SUPPORTED);
    }

    public void setOnBassEventListener(OnBassEventListener listener) {
        mBassEventListener = listener;
    }

    // Used by BroadcastScanViewModel
    public interface OnBassEventListener {
        void onSourceFound(BluetoothLeBroadcastMetadata source);
        void onScanningStateChanged(boolean isScanning);
    }

    public void setOnLocalBroadcastEventListener(OnLocalBroadcastEventListener listener) {
        mLocalBroadcastEventListener = listener;
    }

    // Used by BroadcastScanViewModel
    public interface OnLocalBroadcastEventListener {
        // TODO: Add arguments in methods
        void onBroadcastStarted(int broadcastId);
        void onBroadcastStopped(int broadcastId);
        void onBroadcastUpdated(int broadcastId);
        void onBroadcastMetadataChanged(int broadcastId, BluetoothLeBroadcastMetadata metadata);
    }
}
+28 −16
Original line number Diff line number Diff line
@@ -34,12 +34,10 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.android.bluetooth.leaudio.R;

public class BroadcastItemsAdapter
        extends RecyclerView.Adapter<BroadcastItemsAdapter.BroadcastItemHolder> {
    private List<BluetoothLeBroadcastMetadata> mBroadcastMetadata = new ArrayList<>();
    private final Map<Integer /* broadcastId */, Boolean /* isPlaying */> mBroadcastPlayback =
    private List<BluetoothLeBroadcastMetadata> mBroadcastMetadataList = new ArrayList<>();
    private final Map<Integer /* broadcastId */, Boolean /* isPlaying */> mBroadcastPlaybackMap =
            new HashMap<>();
    private OnItemClickListener mOnItemClickListener;

@@ -57,8 +55,8 @@ public class BroadcastItemsAdapter

    @Override
    public void onBindViewHolder(@NonNull BroadcastItemHolder holder, int position) {
        Integer broadcastId = (Integer) mBroadcastPlayback.keySet().toArray()[position];
        Boolean isPlaying = mBroadcastPlayback.get(broadcastId);
        Integer broadcastId = (Integer) mBroadcastPlaybackMap.keySet().toArray()[position];
        Boolean isPlaying = mBroadcastPlaybackMap.get(broadcastId);

        // Set card color based on the playback state
        if (isPlaying) {
@@ -76,33 +74,47 @@ public class BroadcastItemsAdapter

    @Override
    public int getItemCount() {
        return mBroadcastPlayback.size();
        return mBroadcastPlaybackMap.size();
    }

    public void updateBroadcastsMetadata(List<BluetoothLeBroadcastMetadata> broadcasts) {
        mBroadcastMetadata = broadcasts;
        mBroadcastMetadataList = broadcasts;
        notifyDataSetChanged();
    }

    public void updateBroadcastMetadata(BluetoothLeBroadcastMetadata broadcast) {
        mBroadcastMetadata.removeIf(bc -> (bc.getBroadcastId() == broadcast.getBroadcastId()));
        mBroadcastMetadata.add(broadcast);
        mBroadcastMetadataList.removeIf(bc -> (bc.getBroadcastId() == broadcast.getBroadcastId()));
        mBroadcastMetadataList.add(broadcast);
        notifyDataSetChanged();
    }

    public void addBroadcasts(Integer broadcastId) {
        if (!mBroadcastPlayback.containsKey(broadcastId))
            mBroadcastPlayback.put(broadcastId, false);
        if (!mBroadcastPlaybackMap.containsKey(broadcastId))
            mBroadcastPlaybackMap.put(broadcastId, false);
    }

    public void removeBroadcast(Integer broadcastId) {
        mBroadcastMetadata.removeIf(bc -> (broadcastId.equals(bc.getBroadcastId())));
        mBroadcastPlayback.remove(broadcastId);
        mBroadcastMetadataList.removeIf(bc -> (broadcastId.equals(bc.getBroadcastId())));
        mBroadcastPlaybackMap.remove(broadcastId);
        notifyDataSetChanged();
    }

    public void setBroadcasts(List<BluetoothLeBroadcastMetadata> broadcasts) {
        mBroadcastMetadataList.clear();
        mBroadcastMetadataList.addAll(broadcasts);

        for (BluetoothLeBroadcastMetadata b : broadcasts) {
            int broadcastId = b.getBroadcastId();
            if (mBroadcastPlaybackMap.containsKey(broadcastId)) {
                continue;
            }
            mBroadcastPlaybackMap.remove(broadcastId);
        }
        notifyDataSetChanged();
    }

    public void updateBroadcastPlayback(Integer broadcastId, boolean isPlaying) {
        mBroadcastPlayback.put(broadcastId, isPlaying);
        mBroadcastPlaybackMap.put(broadcastId, isPlaying);
        notifyDataSetChanged();
    }

@@ -125,7 +137,7 @@ public class BroadcastItemsAdapter

                int position = getAdapterPosition();
                if (position != RecyclerView.NO_POSITION) {
                    Integer broadcastId = (Integer) mBroadcastPlayback.keySet().toArray()[position];
                    Integer broadcastId = (Integer) mBroadcastPlaybackMap.keySet().toArray()[position];
                    listener.onItemClick(broadcastId);
                }
            });
+123 −0

File added.

Preview size limit exceeded, changes collapsed.

+168 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading