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

Commit e8bb6cda authored by William Escande's avatar William Escande Committed by Gerrit Code Review
Browse files

Merge "A2dpSink: mainThread for device stateMachine" into main

parents 26489beb c9e48a0b
Loading
Loading
Loading
Loading
+11 −14
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import android.bluetooth.IBluetoothA2dpSink;
import android.content.AttributionSource;
import android.content.Context;
import android.media.AudioManager;
import android.os.Looper;
import android.sysprop.BluetoothProperties;
import android.util.Log;

@@ -54,6 +55,7 @@ public class A2dpSinkService extends ProfileService {
            new ConcurrentHashMap<>(1);

    private final A2dpSinkNativeInterface mNativeInterface;
    private final Looper mLooper;

    private final Object mActiveDeviceLock = new Object();

@@ -74,12 +76,14 @@ public class A2dpSinkService extends ProfileService {

    A2dpSinkService() {
        mNativeInterface = requireNonNull(A2dpSinkNativeInterface.getInstance());
        mLooper = Looper.getMainLooper();
    }

    @VisibleForTesting
    A2dpSinkService(Context ctx, A2dpSinkNativeInterface nativeInterface) {
    A2dpSinkService(Context ctx, A2dpSinkNativeInterface nativeInterface, Looper looper) {
        attachBaseContext(ctx);
        mNativeInterface = requireNonNull(nativeInterface);
        mLooper = looper;
        onCreate();
    }

@@ -132,7 +136,6 @@ public class A2dpSinkService extends ProfileService {

    /**
     * Testing API to inject a mockA2dpSinkService.
     * @hide
     */
    @VisibleForTesting
    public static synchronized void setA2dpSinkService(A2dpSinkService service) {
@@ -437,12 +440,11 @@ public class A2dpSinkService extends ProfileService {
    /**
     * Remove a device's state machine.
     *
     * Called by the state machines when they disconnect.
     * <p>Called by the state machines when they disconnect.
     *
     * Visible for testing so it can be mocked and verified on.
     * <p>Visible for testing so it can be mocked and verified on.
     */
    @VisibleForTesting
    public void removeStateMachine(A2dpSinkStateMachine stateMachine) {
    void removeStateMachine(A2dpSinkStateMachine stateMachine) {
        if (stateMachine == null) {
            return;
        }
@@ -456,7 +458,7 @@ public class A2dpSinkService extends ProfileService {

    protected A2dpSinkStateMachine getOrCreateStateMachine(BluetoothDevice device) {
        A2dpSinkStateMachine newStateMachine =
                new A2dpSinkStateMachine(device, this, mNativeInterface);
                new A2dpSinkStateMachine(mLooper, device, this, mNativeInterface);
        A2dpSinkStateMachine existingStateMachine =
                mDeviceStateMap.putIfAbsent(device, newStateMachine);
        // Given null is not a valid value in our map, ConcurrentHashMap will return null if the
@@ -465,11 +467,6 @@ public class A2dpSinkService extends ProfileService {
        if (existingStateMachine == null) {
            newStateMachine.start();
            return newStateMachine;
        } else {
            // If you try to quit a StateMachine that hasn't been constructed yet, the StateMachine
            // spits out an NPE trying to read a state stack array that only gets made on start().
            // We can just quit the thread made explicitly
            newStateMachine.getHandler().getLooper().quit();
        }
        return existingStateMachine;
    }
@@ -613,7 +610,7 @@ public class A2dpSinkService extends ProfileService {
            return;
        }
        A2dpSinkStateMachine stateMachine = getOrCreateStateMachine(device);
        stateMachine.sendMessage(A2dpSinkStateMachine.STACK_EVENT, event);
        stateMachine.onStackEvent(event);
    }

    private void onAudioStateChanged(StackEvent event) {
@@ -641,6 +638,6 @@ public class A2dpSinkService extends ProfileService {
            return;
        }
        A2dpSinkStateMachine stateMachine = getStateMachineForDevice(device);
        stateMachine.sendMessage(A2dpSinkStateMachine.STACK_EVENT, event);
        stateMachine.onStackEvent(event);
    }
}
+26 −21
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.Intent;
import android.media.AudioFormat;
import android.os.Looper;
import android.os.Message;
import android.util.Log;

@@ -31,24 +32,24 @@ import com.android.bluetooth.BluetoothMetricsProto;
import com.android.bluetooth.Utils;
import com.android.bluetooth.btservice.MetricsLogger;
import com.android.bluetooth.btservice.ProfileService;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;


public class A2dpSinkStateMachine extends StateMachine {
    static final String TAG = "A2dpSinkStateMachine";
class A2dpSinkStateMachine extends StateMachine {
    private static final String TAG = A2dpSinkStateMachine.class.getSimpleName();
    static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);

    // 0->99 Events from Outside
    public static final int CONNECT = 1;
    public static final int DISCONNECT = 2;
    @VisibleForTesting static final int CONNECT = 1;
    @VisibleForTesting static final int DISCONNECT = 2;

    // 100->199 Internal Events
    protected static final int CLEANUP = 100;
    private static final int CONNECT_TIMEOUT = 101;
    @VisibleForTesting static final int CLEANUP = 100;
    @VisibleForTesting static final int CONNECT_TIMEOUT = 101;

    // 200->299 Events from Native
    static final int STACK_EVENT = 200;
    @VisibleForTesting static final int STACK_EVENT = 200;

    static final int CONNECT_TIMEOUT_MS = 10000;

@@ -64,9 +65,12 @@ public class A2dpSinkStateMachine extends StateMachine {
    protected int mMostRecentState = BluetoothProfile.STATE_DISCONNECTED;
    protected BluetoothAudioConfig mAudioConfig = null;

    A2dpSinkStateMachine(BluetoothDevice device, A2dpSinkService service,
    A2dpSinkStateMachine(
            Looper looper,
            BluetoothDevice device,
            A2dpSinkService service,
            A2dpSinkNativeInterface nativeInterface) {
        super(TAG);
        super(TAG, looper);
        mDevice = device;
        mDeviceAddress = Utils.getByteAddress(mDevice);
        mService = service;
@@ -111,20 +115,21 @@ public class A2dpSinkStateMachine extends StateMachine {
        return mDevice;
    }

    /**
     * send the Connect command asynchronously
     */
    public final void connect() {
    /** send the Connect command asynchronously */
    final void connect() {
        sendMessage(CONNECT);
    }

    /**
     * send the Disconnect command asynchronously
     */
    public final void disconnect() {
    /** send the Disconnect command asynchronously */
    final void disconnect() {
        sendMessage(DISCONNECT);
    }

    /** send the stack event asynchronously */
    final void onStackEvent(StackEvent event) {
        sendMessage(STACK_EVENT, event);
    }

    /**
     * Dump the current State Machine to the string builder.
     * @param sb output string
+68 −57
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.media.AudioFormat;
import android.os.test.TestLooper;

import androidx.test.InstrumentationRegistry;
import androidx.test.filters.MediumTest;
@@ -67,6 +68,8 @@ public class A2dpSinkServiceTest {
    private BluetoothDevice mDevice5;
    private BluetoothDevice mDevice6;

    private TestLooper mLooper;

    private static final int TEST_SAMPLE_RATE = 44;
    private static final int TEST_CHANNEL_COUNT = 1;

@@ -75,74 +78,65 @@ public class A2dpSinkServiceTest {
        mTargetContext = InstrumentationRegistry.getTargetContext();
        MockitoAnnotations.initMocks(this);

        mLooper = new TestLooper();

        mAdapter = BluetoothAdapter.getDefaultAdapter();
        assertThat(mAdapter).isNotNull();
        mDevice1 = makeBluetoothDevice("11:11:11:11:11:11");
        mDevice2 = makeBluetoothDevice("22:22:22:22:22:22");
        mDevice3 = makeBluetoothDevice("33:33:33:33:33:33");
        mDevice4 = makeBluetoothDevice("44:44:44:44:44:44");
        mDevice5 = makeBluetoothDevice("55:55:55:55:55:55");
        mDevice6 = makeBluetoothDevice("66:66:66:66:66:66");
        mDevice1 = mAdapter.getRemoteDevice("11:11:11:11:11:11");
        mDevice2 = mAdapter.getRemoteDevice("22:22:22:22:22:22");
        mDevice3 = mAdapter.getRemoteDevice("33:33:33:33:33:33");
        mDevice4 = mAdapter.getRemoteDevice("44:44:44:44:44:44");
        mDevice5 = mAdapter.getRemoteDevice("55:55:55:55:55:55");
        mDevice6 = mAdapter.getRemoteDevice("66:66:66:66:66:66");
        BluetoothDevice[] bondedDevices = new BluetoothDevice[]{
            mDevice1, mDevice2, mDevice3, mDevice4, mDevice5, mDevice6
        };

        // Setup the adapter service and start our service under test
        TestUtils.setAdapterService(mAdapterService);
        doReturn(true).when(mDatabaseManager).setProfileConnectionPolicy(any(), anyInt(), anyInt());

        doReturn(mDatabaseManager).when(mAdapterService).getDatabase();
        doReturn(true, false).when(mAdapterService).isStartedProfile(anyString());
        doReturn(bondedDevices).when(mAdapterService).getBondedDevices();
        when(mDatabaseManager.setProfileConnectionPolicy(any(), anyInt(),
                anyInt())).thenReturn(true);
        setMaxConnectedAudioDevices(1);
        A2dpSinkNativeInterface.setInstance(mNativeInterface);
        TestUtils.startService(mServiceRule, A2dpSinkService.class);
        mService = A2dpSinkService.getA2dpSinkService();
        assertThat(mService).isNotNull();
        doReturn(1).when(mAdapterService).getMaxConnectedAudioDevices();

        TestUtils.setAdapterService(mAdapterService);

        doReturn(true).when(mNativeInterface).setActiveDevice(any());

        mService = new A2dpSinkService(mTargetContext, mNativeInterface, mLooper.getLooper());
        mService.doStart();
        assertThat(mLooper.nextMessage()).isNull();
    }

    @After
    public void tearDown() throws Exception {
        TestUtils.stopService(mServiceRule, A2dpSinkService.class);
        A2dpSinkNativeInterface.setInstance(null);
        mService = A2dpSinkService.getA2dpSinkService();
        assertThat(mService).isNull();
        assertThat(mLooper.nextMessage()).isNull();

        mService.doStop();
        assertThat(A2dpSinkService.getA2dpSinkService()).isNull();
        TestUtils.clearAdapterService(mAdapterService);
    }

    private void syncHandler(int... what) {
        TestUtils.syncHandler(mLooper, what);
    }

    private void setupDeviceConnection(BluetoothDevice device) {
        assertThat(mLooper.nextMessage()).isNull();
        assertThat(mService.getConnectionState(device)).isEqualTo(
                BluetoothProfile.STATE_DISCONNECTED);
        assertThat(mLooper.nextMessage()).isNull();

        assertThat(mService.connect(device)).isTrue();
        sendConnectionEvent(device, StackEvent.CONNECTION_STATE_CONNECTED);
        waitForDeviceProcessing(device);
        syncHandler(-2 /* SM_INIT_CMD */, A2dpSinkStateMachine.CONNECT);
        StackEvent nativeEvent =
                StackEvent.connectionStateChanged(device, StackEvent.CONNECTION_STATE_CONNECTED);
        mService.messageFromNative(nativeEvent);
        syncHandler(A2dpSinkStateMachine.STACK_EVENT);
        assertThat(mService.getConnectionState(device)).isEqualTo(
                BluetoothProfile.STATE_CONNECTED);
    }

    private void sendConnectionEvent(BluetoothDevice device, int newState) {
        StackEvent event = StackEvent.connectionStateChanged(device, newState);
        mService.messageFromNative(event);
    }

    private void waitForDeviceProcessing(BluetoothDevice device) {
        A2dpSinkStateMachine sm = mService.getStateMachineForDevice(device);
        if (sm == null) return;
        TestUtils.waitForLooperToFinishScheduledTask(sm.getHandler().getLooper());
    }

    private BluetoothDevice makeBluetoothDevice(String address) {
        return mAdapter.getRemoteDevice(address);
    }

    /**
     * Set the upper connected device limit
     */
    private void setMaxConnectedAudioDevices(int maxConnectedAudioDevices) {
        when(mAdapterService.getMaxConnectedAudioDevices()).thenReturn(maxConnectedAudioDevices);
    }

    /**
     * Mock the priority of a bluetooth device
     *
@@ -159,7 +153,7 @@ public class A2dpSinkServiceTest {
     */
    @Test
    public void testInitialize() {
        assertThat(A2dpSinkService.getA2dpSinkService()).isNotNull();
        assertThat(A2dpSinkService.getA2dpSinkService()).isEqualTo(mService);
    }

    /**
@@ -204,7 +198,7 @@ public class A2dpSinkServiceTest {
     */
    @Test
    public void testConnectMultipleDevices() {
        setMaxConnectedAudioDevices(5);
        doReturn(5).when(mAdapterService).getMaxConnectedAudioDevices();

        mockDevicePriority(mDevice1, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
        mockDevicePriority(mDevice2, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
@@ -229,9 +223,11 @@ public class A2dpSinkServiceTest {
        setupDeviceConnection(mDevice1);

        assertThat(mService.disconnect(mDevice1)).isTrue();
        waitForDeviceProcessing(mDevice1);
        syncHandler(A2dpSinkStateMachine.DISCONNECT);
        assertThat(mService.getConnectionState(mDevice1)).isEqualTo(
                BluetoothProfile.STATE_DISCONNECTED);

        syncHandler(A2dpSinkStateMachine.CLEANUP, -1 /* SM_QUIT_CMD */);
    }

    /**
@@ -292,7 +288,7 @@ public class A2dpSinkServiceTest {
        StackEvent audioConfigChanged =
                StackEvent.audioConfigChanged(mDevice1, TEST_SAMPLE_RATE, TEST_CHANNEL_COUNT);
        mService.messageFromNative(audioConfigChanged);
        waitForDeviceProcessing(mDevice1);
        syncHandler(A2dpSinkStateMachine.STACK_EVENT);

        BluetoothAudioConfig expected = new BluetoothAudioConfig(TEST_SAMPLE_RATE,
                TEST_CHANNEL_COUNT, AudioFormat.ENCODING_PCM_16BIT);
@@ -406,8 +402,11 @@ public class A2dpSinkServiceTest {
    public void testSetConnectionPolicyDeviceAllowed() {
        assertThat(mService.setConnectionPolicy(mDevice1,
                BluetoothProfile.CONNECTION_POLICY_ALLOWED)).isTrue();
        verify(mDatabaseManager, times(1)).setProfileConnectionPolicy(mDevice1,
                BluetoothProfile.A2DP_SINK, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
        verify(mDatabaseManager)
                .setProfileConnectionPolicy(
                        mDevice1,
                        BluetoothProfile.A2DP_SINK,
                        BluetoothProfile.CONNECTION_POLICY_ALLOWED);
    }

    /**
@@ -417,8 +416,11 @@ public class A2dpSinkServiceTest {
    public void testSetConnectionPolicyDeviceForbiddenWhileNotConnected() {
        assertThat(mService.setConnectionPolicy(mDevice1,
                BluetoothProfile.CONNECTION_POLICY_FORBIDDEN)).isTrue();
        verify(mDatabaseManager, times(1)).setProfileConnectionPolicy(mDevice1,
                BluetoothProfile.A2DP_SINK, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
        verify(mDatabaseManager)
                .setProfileConnectionPolicy(
                        mDevice1,
                        BluetoothProfile.A2DP_SINK,
                        BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
    }

    /**
@@ -432,12 +434,18 @@ public class A2dpSinkServiceTest {

        assertThat(mService.setConnectionPolicy(mDevice1,
                BluetoothProfile.CONNECTION_POLICY_FORBIDDEN)).isTrue();
        verify(mDatabaseManager, times(1)).setProfileConnectionPolicy(mDevice1,
                BluetoothProfile.A2DP_SINK, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
        verify(mDatabaseManager)
                .setProfileConnectionPolicy(
                        mDevice1,
                        BluetoothProfile.A2DP_SINK,
                        BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);

        waitForDeviceProcessing(mDevice1);
        syncHandler(A2dpSinkStateMachine.DISCONNECT);
        verify(mNativeInterface).disconnectA2dpSink(eq(mDevice1));
        assertThat(mService.getConnectionState(mDevice1)).isEqualTo(
                BluetoothProfile.STATE_DISCONNECTED);

        syncHandler(A2dpSinkStateMachine.CLEANUP, -1 /* SM_QUIT_CMD */);
    }

    /**
@@ -447,8 +455,11 @@ public class A2dpSinkServiceTest {
    public void testSetConnectionPolicyDeviceUnknown() {
        assertThat(mService.setConnectionPolicy(mDevice1,
                BluetoothProfile.CONNECTION_POLICY_UNKNOWN)).isTrue();
        verify(mDatabaseManager, times(1)).setProfileConnectionPolicy(mDevice1,
                BluetoothProfile.A2DP_SINK, BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
        verify(mDatabaseManager)
                .setProfileConnectionPolicy(
                        mDevice1,
                        BluetoothProfile.A2DP_SINK,
                        BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
    }

    /**
+80 −77

File changed.

Preview size limit exceeded, changes collapsed.