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

Commit a3f62c80 authored by Henri Chataing's avatar Henri Chataing Committed by Gerrit Code Review
Browse files

Merge "A2dpService: Share looper with A2dpStateMachine" into main

parents 8a0782fb b7f592f2
Loading
Loading
Loading
Loading
+17 −11
Original line number Diff line number Diff line
@@ -91,9 +91,10 @@ public class A2dpService extends ProfileService {
    private final AudioManager mAudioManager;
    private final DatabaseManager mDatabaseManager;
    private final CompanionDeviceManager mCompanionDeviceManager;
    private final Looper mLooper;
    private final Handler mHandler;

    private HandlerThread mStateMachinesThread;
    private Handler mHandler = null;

    @VisibleForTesting ServiceFactory mFactory = new ServiceFactory();
    private A2dpCodecConfig mA2dpCodecConfig;
@@ -122,19 +123,21 @@ public class A2dpService extends ProfileService {
            new AudioManagerAudioDeviceCallback();

    public A2dpService(AdapterService adapterService) {
        this(adapterService, A2dpNativeInterface.getInstance());
        this(adapterService, A2dpNativeInterface.getInstance(), Looper.getMainLooper());
    }

    @VisibleForTesting
    A2dpService(AdapterService adapterService, A2dpNativeInterface nativeInterface) {
    A2dpService(AdapterService adapterService, A2dpNativeInterface nativeInterface, Looper looper) {
        super(requireNonNull(adapterService));
        mAdapterService = adapterService;
        mNativeInterface = requireNonNull(nativeInterface);
        mDatabaseManager = requireNonNull(mAdapterService.getDatabase());
        mAudioManager = requireNonNull(getSystemService(AudioManager.class));
        mLooper = requireNonNull(looper);

        // Some platform may not have the FEATURE_COMPANION_DEVICE_SETUP
        mCompanionDeviceManager = getSystemService(CompanionDeviceManager.class);
        mHandler = new Handler(mLooper);
    }

    public static boolean isEnabled() {
@@ -163,10 +166,12 @@ public class A2dpService extends ProfileService {

        // Step 3: Start handler thread for state machines
        // Setup Handler.
        mHandler = new Handler(Looper.getMainLooper());
        mStateMachines.clear();

        if (!Flags.a2dpServiceLooper()) {
            mStateMachinesThread = new HandlerThread("A2dpService.StateMachines");
            mStateMachinesThread.start();
        }

        // Step 4: Setup codec config
        mA2dpCodecConfig = new A2dpCodecConfig(this, mNativeInterface);
@@ -232,10 +237,8 @@ public class A2dpService extends ProfileService {
                // Do not rethrow as we are shutting down anyway
            }
        }
        if (mHandler != null) {

        mHandler.removeCallbacksAndMessages(null);
            mHandler = null;
        }

        // Step 2: Reset maximum number of connected audio devices
        mMaxConnectedAudioDevices = 1;
@@ -1040,7 +1043,10 @@ public class A2dpService extends ProfileService {
            Log.d(TAG, "Creating a new state machine for " + device);
            sm =
                    A2dpStateMachine.make(
                            device, this, mNativeInterface, mStateMachinesThread.getLooper());
                            device,
                            this,
                            mNativeInterface,
                            Flags.a2dpServiceLooper() ? mLooper : mStateMachinesThread.getLooper());
            mStateMachines.put(device, sm);
            return sm;
        }
+85 −9
Original line number Diff line number Diff line
@@ -36,16 +36,19 @@ import android.media.AudioManager;
import android.media.BluetoothProfileConnectionInfo;
import android.os.Looper;
import android.os.ParcelUuid;
import android.os.test.TestLooper;
import android.platform.test.flag.junit.FlagsParameterization;
import android.platform.test.flag.junit.SetFlagsRule;

import androidx.test.InstrumentationRegistry;
import androidx.test.filters.MediumTest;
import androidx.test.runner.AndroidJUnit4;

import com.android.bluetooth.TestUtils;
import com.android.bluetooth.btservice.ActiveDeviceManager;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.SilenceDeviceManager;
import com.android.bluetooth.btservice.storage.DatabaseManager;
import com.android.bluetooth.flags.Flags;

import org.hamcrest.Matcher;
import org.hamcrest.core.AllOf;
@@ -61,12 +64,15 @@ import org.mockito.hamcrest.MockitoHamcrest;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;

import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
import platform.test.runner.parameterized.Parameters;

import java.time.Duration;
import java.util.Arrays;
import java.util.List;

@MediumTest
@RunWith(AndroidJUnit4.class)
@RunWith(ParameterizedAndroidJunit4.class)
public class A2dpServiceTest {
    private static final int MAX_CONNECTED_AUDIO_DEVICES = 5;
    private static final Duration TIMEOUT = Duration.ofSeconds(1);
@@ -75,6 +81,7 @@ public class A2dpServiceTest {
    private static final BluetoothDevice sTestDevice =
            sAdapter.getRemoteDevice("00:01:02:03:04:05");

    @Rule public final SetFlagsRule mSetFlagsRule;
    @Rule public MockitoRule mockitoRule = MockitoJUnit.rule();

    @Mock private A2dpNativeInterface mMockNativeInterface;
@@ -83,13 +90,25 @@ public class A2dpServiceTest {
    @Mock private AudioManager mAudioManager;
    @Mock private DatabaseManager mDatabaseManager;
    @Mock private SilenceDeviceManager mSilenceDeviceManager;

    private InOrder mInOrder = null;

    private TestLooper mLooper;
    private A2dpService mA2dpService;

    @Parameters(name = "{0}")
    public static List<FlagsParameterization> getParams() {
        return FlagsParameterization.allCombinationsOf(Flags.FLAG_A2DP_SERVICE_LOOPER);
    }

    public A2dpServiceTest(FlagsParameterization flags) {
        mSetFlagsRule = new SetFlagsRule(flags);
    }

    @Before
    public void setUp() throws Exception {
        mInOrder = inOrder(mAdapterService);
        mLooper = new TestLooper();

        TestUtils.mockGetSystemService(
                mAdapterService, Context.AUDIO_SERVICE, AudioManager.class, mAudioManager);
@@ -108,7 +127,7 @@ public class A2dpServiceTest {
        doReturn(mActiveDeviceManager).when(mAdapterService).getActiveDeviceManager();
        doReturn(mSilenceDeviceManager).when(mAdapterService).getSilenceDeviceManager();

        mA2dpService = new A2dpService(mAdapterService, mMockNativeInterface);
        mA2dpService = new A2dpService(mAdapterService, mMockNativeInterface, mLooper.getLooper());
        mA2dpService.start();
        mA2dpService.setAvailable(true);

@@ -122,15 +141,20 @@ public class A2dpServiceTest {
        doReturn(new ParcelUuid[] {BluetoothUuid.A2DP_SINK})
                .when(mAdapterService)
                .getRemoteUuids(any(BluetoothDevice.class));

        if (!Flags.a2dpServiceLooper()) {
            mLooper.startAutoDispatch();
        }
    }

    @After
    public void tearDown() {
        // A2dpService handler is running on main looper. Calling `stop` remove the messages but
        // assume it is already on the correct thread.
        // Calling it from another thread may lead to having messages still being processed and
        // executed after tearDown is called.
        InstrumentationRegistry.getInstrumentation().runOnMainSync(mA2dpService::stop);
        if (Flags.a2dpServiceLooper()) {
            assertThat(mLooper.dispatchAll()).isEqualTo(0);
        } else {
            mLooper.stopAutoDispatchAndIgnoreExceptions();
        }
        mA2dpService.stop();
    }

    @SafeVarargs
@@ -161,6 +185,7 @@ public class A2dpServiceTest {
        verify(mMockNativeInterface).setActiveDevice(sTestDevice);

        mA2dpService.stop();
        dispatchAtLeastOneMessage();

        // Verify that setActiveDevice(null) was called during shutdown
        verify(mMockNativeInterface).setActiveDevice(null);
@@ -291,6 +316,7 @@ public class A2dpServiceTest {

        // Send a connect request
        assertThat(mA2dpService.connect(sTestDevice)).isTrue();
        dispatchAtLeastOneMessage();

        // Verify the connection state broadcast, and that we are in Connecting state
        verifyConnectionStateIntent(
@@ -300,6 +326,10 @@ public class A2dpServiceTest {
        assertThat(mA2dpService.getConnectionState(sTestDevice))
                .isEqualTo(BluetoothProfile.STATE_CONNECTING);

        // Verify a timeout after 1sec.
        moveTimeForward(TIMEOUT.toMillis());
        dispatchAtLeastOneMessage();

        // Verify the connection state broadcast, and that we are in Disconnected state
        verifyConnectionStateIntent(
                sTestDevice,
@@ -322,6 +352,7 @@ public class A2dpServiceTest {

        // Send a connect request
        assertThat(mA2dpService.connect(sTestDevice)).isTrue();
        dispatchAtLeastOneMessage();

        // Verify the connection state broadcast, and that we are in Connecting state
        verifyConnectionStateIntent(
@@ -336,6 +367,7 @@ public class A2dpServiceTest {
        connCompletedEvent.device = sTestDevice;
        connCompletedEvent.valueInt = A2dpStackEvent.CONNECTION_STATE_CONNECTED;
        mA2dpService.messageFromNative(connCompletedEvent);
        dispatchAtLeastOneMessage();

        // Verify the connection state broadcast, and that we are in Connected state
        verifyConnectionStateIntent(
@@ -348,6 +380,7 @@ public class A2dpServiceTest {

        // Send a disconnect request
        assertThat(mA2dpService.disconnect(sTestDevice)).isTrue();
        dispatchAtLeastOneMessage();

        // Verify the connection state broadcast, and that we are in Disconnecting state
        verifyConnectionStateIntent(
@@ -362,6 +395,7 @@ public class A2dpServiceTest {
        connCompletedEvent.device = sTestDevice;
        connCompletedEvent.valueInt = A2dpStackEvent.CONNECTION_STATE_DISCONNECTED;
        mA2dpService.messageFromNative(connCompletedEvent);
        dispatchAtLeastOneMessage();

        // Verify the connection state broadcast, and that we are in Disconnected state
        verifyConnectionStateIntent(
@@ -393,6 +427,7 @@ public class A2dpServiceTest {
                    .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
            // Send a connect request
            assertThat(mA2dpService.connect(testDevice)).isTrue();
            dispatchAtLeastOneMessage();
            // Verify the connection state broadcast, and that we are in Connecting state
            verifyConnectionStateIntent(
                    testDevice,
@@ -406,7 +441,7 @@ public class A2dpServiceTest {
            connCompletedEvent.device = testDevice;
            connCompletedEvent.valueInt = A2dpStackEvent.CONNECTION_STATE_CONNECTED;
            mA2dpService.messageFromNative(connCompletedEvent);

            dispatchAtLeastOneMessage();
            // Verify the connection state broadcast, and that we are in Connected state
            verifyConnectionStateIntent(
                    testDevice,
@@ -455,7 +490,9 @@ public class A2dpServiceTest {
        assertThat(mA2dpService.getConnectionState(sTestDevice))
                .isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
        assertThat(mA2dpService.getDevices()).contains(sTestDevice);

        mA2dpService.bondStateChanged(sTestDevice, BluetoothDevice.BOND_NONE);
        dispatchAtLeastOneMessage();
        assertThat(mA2dpService.getDevices()).doesNotContain(sTestDevice);

        // A2DP stack event: CONNECTION_STATE_CONNECTED - state machine should be created
@@ -481,7 +518,9 @@ public class A2dpServiceTest {
        assertThat(mA2dpService.getConnectionState(sTestDevice))
                .isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
        assertThat(mA2dpService.getDevices()).contains(sTestDevice);

        mA2dpService.bondStateChanged(sTestDevice, BluetoothDevice.BOND_NONE);
        dispatchAtLeastOneMessage();
        assertThat(mA2dpService.getDevices()).doesNotContain(sTestDevice);

        // A2DP stack event: CONNECTION_STATE_DISCONNECTING - state machine should not be created
@@ -584,6 +623,7 @@ public class A2dpServiceTest {
                .isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
        assertThat(mA2dpService.getDevices()).contains(sTestDevice);
        mA2dpService.bondStateChanged(sTestDevice, BluetoothDevice.BOND_NONE);
        dispatchAtLeastOneMessage();
        assertThat(mA2dpService.getDevices()).doesNotContain(sTestDevice);
    }

@@ -645,6 +685,7 @@ public class A2dpServiceTest {
        assertThat(mA2dpService.getDevices()).contains(sTestDevice);
        // Device unbond - state machine is removed
        mA2dpService.bondStateChanged(sTestDevice, BluetoothDevice.BOND_NONE);
        dispatchAtLeastOneMessage();
        assertThat(mA2dpService.getDevices()).doesNotContain(sTestDevice);
    }

@@ -757,6 +798,8 @@ public class A2dpServiceTest {
        assertThat(mA2dpService.getActiveDevice()).isEqualTo(sTestDevice);

        assertThat(mA2dpService.disconnect(sTestDevice)).isTrue();
        dispatchAtLeastOneMessage();

        verifyConnectionStateIntent(
                sTestDevice,
                BluetoothProfile.STATE_DISCONNECTING,
@@ -785,6 +828,8 @@ public class A2dpServiceTest {
        assertThat(mA2dpService.getActiveDevice()).isEqualTo(sTestDevice);

        assertThat(mA2dpService.disconnect(sTestDevice)).isTrue();
        dispatchAtLeastOneMessage();

        verifyConnectionStateIntent(
                sTestDevice,
                BluetoothProfile.STATE_DISCONNECTING,
@@ -1009,6 +1054,7 @@ public class A2dpServiceTest {

        // Send a connect request
        assertThat(mA2dpService.connect(device)).isTrue();
        dispatchAtLeastOneMessage();

        // Verify the connection state broadcast, and that we are in Connecting state
        verifyConnectionStateIntent(
@@ -1025,6 +1071,7 @@ public class A2dpServiceTest {
        connCompletedEvent.device = device;
        connCompletedEvent.valueInt = A2dpStackEvent.CONNECTION_STATE_CONNECTED;
        mA2dpService.messageFromNative(connCompletedEvent);
        dispatchAtLeastOneMessage();

        // Verify the connection state broadcast, and that we are in Connected state
        verifyConnectionStateIntent(
@@ -1038,6 +1085,8 @@ public class A2dpServiceTest {
        for (BluetoothDevice prevDevice : prevConnectedDevices) {
            assertThat(mA2dpService.getConnectedDevices()).contains(prevDevice);
        }

        dispatchNoMessages();
    }

    private void generateConnectionMessageFromNative(
@@ -1047,8 +1096,10 @@ public class A2dpServiceTest {
        stackEvent.device = device;
        stackEvent.valueInt = newConnectionState;
        mA2dpService.messageFromNative(stackEvent);
        dispatchAtLeastOneMessage();
        // Verify the connection state broadcast
        verifyConnectionStateIntent(device, newConnectionState, oldConnectionState);
        dispatchNoMessages();
    }

    private void generateUnexpectedConnectionMessageFromNative(
@@ -1061,6 +1112,7 @@ public class A2dpServiceTest {
        // Verify the connection state broadcast
        mInOrder.verify(mAdapterService, timeout(TIMEOUT.toMillis()).times(0))
                .sendBroadcast(any(), any(), any());
        dispatchNoMessages();
    }

    private void generateAudioMessageFromNative(
@@ -1070,12 +1122,14 @@ public class A2dpServiceTest {
        stackEvent.device = device;
        stackEvent.valueInt = audioStackEvent;
        mA2dpService.messageFromNative(stackEvent);
        dispatchAtLeastOneMessage();
        // Verify the audio state broadcast
        verifyIntentSent(
                hasAction(BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED),
                hasExtra(BluetoothDevice.EXTRA_DEVICE, device),
                hasExtra(BluetoothProfile.EXTRA_STATE, newAudioState),
                hasExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, oldAudioState));
        dispatchNoMessages();
    }

    private void generateUnexpectedAudioMessageFromNative(
@@ -1097,6 +1151,7 @@ public class A2dpServiceTest {
        stackEvent.device = device;
        stackEvent.codecStatus = codecStatus;
        mA2dpService.messageFromNative(stackEvent);
        dispatchAtLeastOneMessage();
        verifyIntentSent(
                hasAction(BluetoothA2dp.ACTION_CODEC_CONFIG_CHANGED),
                hasExtra(BluetoothDevice.EXTRA_DEVICE, device),
@@ -1263,4 +1318,25 @@ public class A2dpServiceTest {
                .setCodecSpecific4(codecSpecific4)
                .build();
    }

    // Dispatch messages for the A2dpService looper, and validate
    // that at least one message was handled.
    private void dispatchAtLeastOneMessage() {
        if (Flags.a2dpServiceLooper()) {
            assertThat(mLooper.dispatchAll()).isGreaterThan(0);
        }
    }

    // Validate that no messages are pending on the A2dpService looper.
    private void dispatchNoMessages() {
        if (Flags.a2dpServiceLooper()) {
            assertThat(mLooper.dispatchAll()).isEqualTo(0);
        }
    }

    private void moveTimeForward(long millis) {
        if (Flags.a2dpServiceLooper()) {
            mLooper.moveTimeForward(millis);
        }
    }
}