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

Commit 588c8e91 authored by Rongxuan Liu's avatar Rongxuan Liu
Browse files

[le audio] Add new APIs for broadcast assistant on source lost

onSourceLost is introduced to allow app to monitor the remote source sync
status
Flagged by: leaudio_broadcast_monitor_source_sync_status

Bug: 298665925
Bug: 307406671
Test: atest BluetoothLeBroadcastAssistantTest
Test: atest BassClientStateMachineTest
Test: manual test callback with LeAudioTestApp
Change-Id: Ide7d36d9ae9fbd209a380ed281c18a13ca5511c2
parent 0da4cf20
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -40,4 +40,5 @@ interface IBluetoothLeBroadcastAssistantCallback {
    void onSourceRemoveFailed(in BluetoothDevice sink, in int sourceId, in int reason);
    void onReceiveStateChanged(in BluetoothDevice sink, in int sourceId,
            in BluetoothLeBroadcastReceiveState state);
    void onSourceLost(in int broadcastId);
}
+27 −2
Original line number Diff line number Diff line
@@ -56,6 +56,8 @@ import com.android.bluetooth.btservice.ProfileService;
import com.android.bluetooth.btservice.ServiceFactory;
import com.android.bluetooth.btservice.storage.DatabaseManager;
import com.android.bluetooth.csip.CsipSetCoordinatorService;
import com.android.bluetooth.flags.FeatureFlags;
import com.android.bluetooth.flags.FeatureFlagsImpl;
import com.android.bluetooth.le_audio.LeAudioService;
import com.android.internal.annotations.VisibleForTesting;

@@ -89,6 +91,7 @@ public class BassClientService extends ProfileService {
    private final Map<BluetoothDevice, List<Integer>> mGroupManagedSources =
            new ConcurrentHashMap<>();
    private final Map<BluetoothDevice, List<Integer>> mActiveSourceMap = new ConcurrentHashMap<>();
    private final FeatureFlags mFeatureFlags;

    private HandlerThread mStateMachinesThread;
    private HandlerThread mCallbackHandlerThread;
@@ -122,6 +125,17 @@ public class BassClientService extends ProfileService {
    @VisibleForTesting
    ServiceFactory mServiceFactory = new ServiceFactory();

    BassClientService() {
        mFeatureFlags = new FeatureFlagsImpl();
    }

    @VisibleForTesting
    BassClientService(Context ctx, FeatureFlags featureFlags) {
        attachBaseContext(ctx);
        mFeatureFlags = featureFlags;
        onCreate();
    }

    public static boolean isEnabled() {
        return BluetoothProperties.isProfileBapBroadcastAssistEnabled().orElse(false);
    }
@@ -701,8 +715,10 @@ public class BassClientService extends ProfileService {
                return null;
            }
            log("Creating a new state machine for " + device);
            stateMachine = BassObjectsFactory.getInstance().makeStateMachine(
                    device, this, mStateMachinesThread.getLooper());
            stateMachine =
                    BassObjectsFactory.getInstance()
                            .makeStateMachine(
                                    device, this, mStateMachinesThread.getLooper(), mFeatureFlags);
            mStateMachines.put(device, stateMachine);
            return stateMachine;
        }
@@ -1536,6 +1552,7 @@ public class BassClientService extends ProfileService {
        private static final int MSG_SOURCE_REMOVED = 10;
        private static final int MSG_SOURCE_REMOVED_FAILED = 11;
        private static final int MSG_RECEIVESTATE_CHANGED = 12;
        private static final int MSG_SOURCE_LOST = 13;

        private final RemoteCallbackList<IBluetoothLeBroadcastAssistantCallback>
                mCallbacks = new RemoteCallbackList<>();
@@ -1661,6 +1678,9 @@ public class BassClientService extends ProfileService {
                            (BluetoothLeBroadcastReceiveState) param.mObj2;
                    callback.onReceiveStateChanged(sink, sourceId, state);
                    break;
                case MSG_SOURCE_LOST:
                    callback.onSourceLost(sourceId);
                    break;
                default:
                    Log.e(TAG, "Invalid msg: " + msg.what);
                    break;
@@ -1810,6 +1830,11 @@ public class BassClientService extends ProfileService {
                            + subgroupState);
            obtainMessage(MSG_RECEIVESTATE_CHANGED, 0, sourceId, param).sendToTarget();
        }

        void notifySourceLost(int broadcastId) {
            sEventLogger.logd(TAG, "notifySourceLost: " + ", broadcastId: " + broadcastId);
            obtainMessage(MSG_SOURCE_LOST, 0, broadcastId).sendToTarget();
        }
    }

    @Override
+27 −7
Original line number Diff line number Diff line
@@ -54,6 +54,7 @@ import com.android.bluetooth.BluetoothMethodProxy;
import com.android.bluetooth.Utils;
import com.android.bluetooth.btservice.ProfileService;
import com.android.bluetooth.btservice.ServiceFactory;
import com.android.bluetooth.flags.FeatureFlags;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
@@ -70,6 +71,7 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Scanner;
import java.util.UUID;
import java.util.stream.IntStream;
@@ -126,6 +128,8 @@ public class BassClientStateMachine extends StateMachine {
    private final Connected mConnected = new Connected();
    private final Connecting mConnecting = new Connecting();
    private final ConnectedProcessing mConnectedProcessing = new ConnectedProcessing();
    private final FeatureFlags mFeatureFlags;

    @VisibleForTesting
    final List<BluetoothGattCharacteristic> mBroadcastCharacteristics =
            new ArrayList<BluetoothGattCharacteristic>();
@@ -175,15 +179,20 @@ public class BassClientStateMachine extends StateMachine {
    @VisibleForTesting
    BluetoothGattTestableWrapper mBluetoothGatt = null;
    BluetoothGattCallback mGattCallback = null;
    PeriodicAdvertisingCallback mLocalPeriodicAdvCallback = new PACallback();
    @VisibleForTesting PeriodicAdvertisingCallback mLocalPeriodicAdvCallback = new PACallback();
    int mMaxSingleAttributeWriteValueLen = 0;

    BassClientStateMachine(BluetoothDevice device, BassClientService svc, Looper looper,
            int connectTimeoutMs) {
    BassClientStateMachine(
            BluetoothDevice device,
            BassClientService svc,
            Looper looper,
            int connectTimeoutMs,
            FeatureFlags featureFlags) {
        super(TAG + "(" + device.toString() + ")", looper);
        mDevice = device;
        mService = svc;
        mConnectTimeoutMs = connectTimeoutMs;
        mFeatureFlags = Objects.requireNonNull(featureFlags, "Feature Flags cannot be null");
        addState(mDisconnected);
        addState(mConnected);
        addState(mConnecting);
@@ -205,11 +214,15 @@ public class BassClientStateMachine extends StateMachine {
        Binder.restoreCallingIdentity(token);
    }

    static BassClientStateMachine make(BluetoothDevice device,
            BassClientService svc, Looper looper) {
    static BassClientStateMachine make(
            BluetoothDevice device,
            BassClientService svc,
            Looper looper,
            FeatureFlags featureFlags) {
        Log.d(TAG, "make for device " + device);
        BassClientStateMachine BassclientSm = new BassClientStateMachine(device, svc, looper,
                BassConstants.CONNECT_TIMEOUT_MS);
        BassClientStateMachine BassclientSm =
                new BassClientStateMachine(
                        device, svc, looper, BassConstants.CONNECT_TIMEOUT_MS, featureFlags);
        BassclientSm.start();
        return BassclientSm;
    }
@@ -1099,6 +1112,13 @@ public class BassClientStateMachine extends StateMachine {
        @Override
        public void onSyncLost(int syncHandle) {
            log("OnSyncLost" + syncHandle);
            if (mFeatureFlags.leaudioBroadcastMonitorSourceSyncStatus()) {
                int broadcastId = mService.getBroadcastIdForSyncHandle(syncHandle);
                if (broadcastId != BassConstants.INVALID_BROADCAST_ID) {
                    log("Notify broadcast source lost, broadcast id: " + broadcastId);
                    mService.getCallbacks().notifySourceLost(broadcastId);
                }
            }
            cancelActiveSync(syncHandle);
        }

+8 −3
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import android.os.Looper;
import android.util.Log;

import com.android.bluetooth.Utils;
import com.android.bluetooth.flags.FeatureFlags;
import com.android.internal.annotations.VisibleForTesting;

/**
@@ -68,11 +69,15 @@ public class BassObjectsFactory {
     * @param device the remote device associated with this state machine
     * @param svc the bass client service
     * @param looper the thread that the state machine is supposed to run on
     * @param featureFlags feature flag from bass client service
     * @return a state machine that is initialized and started, ready to go
     */
    public BassClientStateMachine makeStateMachine(BluetoothDevice device,
            BassClientService svc, Looper looper) {
        return BassClientStateMachine.make(device, svc, looper);
    public BassClientStateMachine makeStateMachine(
            BluetoothDevice device,
            BassClientService svc,
            Looper looper,
            FeatureFlags featureFlags) {
        return BassClientStateMachine.make(device, svc, looper, featureFlags);
    }

    /**
+20 −11
Original line number Diff line number Diff line
@@ -223,15 +223,24 @@ public class BassClientServiceTest {
        }).when(mAdapterService).getBondedDevices();

        // Mock methods in BassObjectsFactory
        doAnswer(invocation -> {
        doAnswer(
                        invocation -> {
                            assertThat(mCurrentDevice).isNotNull();
            final BassClientStateMachine stateMachine = mock(BassClientStateMachine.class);
                            final BassClientStateMachine stateMachine =
                                    mock(BassClientStateMachine.class);
                            doReturn(new ArrayList<>()).when(stateMachine).getAllSources();
            doReturn(TEST_NUM_SOURCES).when(stateMachine).getMaximumSourceCapacity();
            doReturn((BluetoothDevice)invocation.getArgument(0)).when(stateMachine).getDevice();
            mStateMachines.put((BluetoothDevice)invocation.getArgument(0), stateMachine);
                            doReturn(TEST_NUM_SOURCES)
                                    .when(stateMachine)
                                    .getMaximumSourceCapacity();
                            doReturn((BluetoothDevice) invocation.getArgument(0))
                                    .when(stateMachine)
                                    .getDevice();
                            mStateMachines.put(
                                    (BluetoothDevice) invocation.getArgument(0), stateMachine);
                            return stateMachine;
        }).when(mObjectsFactory).makeStateMachine(any(), any(), any());
                        })
                .when(mObjectsFactory)
                .makeStateMachine(any(), any(), any(), any());
        doReturn(mBluetoothLeScannerWrapper).when(mObjectsFactory)
                .getBluetoothLeScannerWrapper(any());

@@ -337,8 +346,8 @@ public class BassClientServiceTest {
        mCurrentDevice = TestUtils.getTestDevice(mBluetoothAdapter, 0);

        assertThat(mBassClientService.connect(mCurrentDevice)).isTrue();
        verify(mObjectsFactory).makeStateMachine(
                eq(mCurrentDevice), eq(mBassClientService), any());
        verify(mObjectsFactory)
                .makeStateMachine(eq(mCurrentDevice), eq(mBassClientService), any(), any());
        BassClientStateMachine stateMachine = mStateMachines.get(mCurrentDevice);
        assertThat(stateMachine).isNotNull();
        verify(stateMachine).sendMessage(BassClientStateMachine.CONNECT);
Loading