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

Commit d46d54a7 authored by Łukasz Rymanowski's avatar Łukasz Rymanowski
Browse files

leaudio: Fix authorization on MCS/TBS after unbonding

When new LeAudio device pairs, MCS and TBS are in special mode allowing
to do CCC writes and reads before LeAudio is fully connected.

It is not behaving the same when doing unbond and bond again without
disabling Bluetooth as MCS and TBS keeps authorization state to REJECT.

This patch fixes that

Bug: 321089661
Bug: 323339440
Test: atest LeAudioServiceTest
Change-Id: I88f3996cee76fa65590fb9de825856e85a57adf0
parent c148cf66
Loading
Loading
Loading
Loading
+20 −0
Original line number Diff line number Diff line
@@ -2875,6 +2875,7 @@ public class LeAudioService extends ProfileService {
                return;
            }
            removeStateMachine(device);
            removeAuthorizationInfoForRelatedProfiles(device);
        }
    }

@@ -2962,6 +2963,7 @@ public class LeAudioService extends ProfileService {
                Log.d(TAG, device + " is unbond. Remove state machine");
            }
            removeStateMachine(device);
            removeAuthorizationInfoForRelatedProfiles(device);
        }

        if (!isScannerNeeded()) {
@@ -3341,6 +3343,23 @@ public class LeAudioService extends ProfileService {
        }
    }

    void removeAuthorizationInfoForRelatedProfiles(BluetoothDevice device) {
        if (!mFeatureFlags.leaudioMcsTbsAuthorizationRebondFix()) {
            Log.i(TAG, "leaudio_mcs_tbs_authorization_rebond_fix is disabled");
            return;
        }

        McpService mcpService = getMcpService();
        if (mcpService != null) {
            mcpService.removeDeviceAuthorizationInfo(device);
        }

        TbsService tbsService = getTbsService();
        if (tbsService != null) {
            tbsService.removeDeviceAuthorizationInfo(device);
        }
    }

    /**
     * This function is called when the framework registers a callback with the service for this
     * first time. This is used as an indication that Bluetooth has been enabled.
@@ -3499,6 +3518,7 @@ public class LeAudioService extends ProfileService {
        }

        setAuthorizationForRelatedProfiles(device, false);
        removeAuthorizationInfoForRelatedProfiles(device);
    }

    private void notifyGroupNodeRemoved(BluetoothDevice device, int groupId) {
+11 −0
Original line number Diff line number Diff line
@@ -200,6 +200,17 @@ public class McpService extends ProfileService {
        setDeviceAuthorized(device, false);
    }

    /**
     * Remove authorization information for the device.
     *
     * @param device device to remove from the service information
     * @hide
     */
    public void removeDeviceAuthorizationInfo(BluetoothDevice device) {
        Log.i(TAG, "removeDeviceAuthorizationInfo(): device: " + device);
        mDeviceAuthorizations.remove(device);
    }

    public void setDeviceAuthorized(BluetoothDevice device, boolean isAuthorized) {
        Log.i(TAG, "\tsetDeviceAuthorized(): device: " + device + ", isAuthorized: "
                + isAuthorized);
+11 −0
Original line number Diff line number Diff line
@@ -143,6 +143,17 @@ public class TbsService extends ProfileService {
        setDeviceAuthorized(device, false);
    }

    /**
     * Remove authorization information for the device.
     *
     * @param device device to remove from the service information
     * @hide
     */
    public void removeDeviceAuthorizationInfo(BluetoothDevice device) {
        Log.i(TAG, "removeDeviceAuthorizationInfo(): device: " + device);
        mDeviceAuthorizations.remove(device);
    }

    /**
     * Sets device authorization for TBS.
     *
+134 −0
Original line number Diff line number Diff line
@@ -198,6 +198,7 @@ public class LeAudioServiceTest {
        mFakeFlagsImpl.setFlag(Flags.FLAG_LEAUDIO_UNICAST_INACTIVATE_DEVICE_BASED_ON_CONTEXT, false);
        mFakeFlagsImpl.setFlag(Flags.FLAG_AUDIO_ROUTING_CENTRALIZATION, false);
        mFakeFlagsImpl.setFlag(Flags.FLAG_LEAUDIO_BROADCAST_AUDIO_HANDOVER_POLICIES, false);
        mFakeFlagsImpl.setFlag(Flags.FLAG_LEAUDIO_MCS_TBS_AUTHORIZATION_REBOND_FIX, false);
        mService.setFeatureFlags(mFakeFlagsImpl);

        mService.mAudioManager = mAudioManager;
@@ -804,6 +805,139 @@ public class LeAudioServiceTest {
        assertThat(mService.getDevices().contains(mLeftDevice)).isFalse();
    }

    /** Test that authorization info is removed from TBS and MCS after the device is unbond. */
    @Test
    public void testAuthorizationInfoRemovedFromTbsMcsOnUnbondEvents() {
        mFakeFlagsImpl.setFlag(Flags.FLAG_AUDIO_ROUTING_CENTRALIZATION, true);
        mFakeFlagsImpl.setFlag(Flags.FLAG_LEAUDIO_MCS_TBS_AUTHORIZATION_REBOND_FIX, true);

        // Update the device priority so okToConnect() returns true
        when(mDatabaseManager.getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.LE_AUDIO))
                .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
        when(mDatabaseManager.getProfileConnectionPolicy(mRightDevice, BluetoothProfile.LE_AUDIO))
                .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
        when(mDatabaseManager.getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.LE_AUDIO))
                .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
        doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class));
        doReturn(true).when(mNativeInterface).disconnectLeAudio(any(BluetoothDevice.class));

        // Create device descriptor with connect request
        assertWithMessage("Connect failed").that(mService.connect(mLeftDevice)).isTrue();

        // Unbond received in CONNECTION_STATE_CONNECTING state
        generateConnectionMessageFromNative(
                mLeftDevice,
                BluetoothProfile.STATE_CONNECTING,
                BluetoothProfile.STATE_DISCONNECTED);
        assertThat(mService.getConnectionState(mLeftDevice))
                .isEqualTo(BluetoothProfile.STATE_CONNECTING);
        assertThat(mService.getDevices().contains(mLeftDevice)).isTrue();

        // Device unbond
        doReturn(BluetoothDevice.BOND_NONE)
                .when(mAdapterService)
                .getBondState(any(BluetoothDevice.class));
        mService.bondStateChanged(mLeftDevice, BluetoothDevice.BOND_NONE);
        assertThat(mService.getDevices().contains(mLeftDevice)).isTrue();
        verifyConnectionStateIntent(
                TIMEOUT_MS,
                mLeftDevice,
                BluetoothProfile.STATE_DISCONNECTED,
                BluetoothProfile.STATE_CONNECTING);
        verify(mTbsService, times(1)).removeDeviceAuthorizationInfo(mLeftDevice);
        verify(mMcpService, times(1)).removeDeviceAuthorizationInfo(mLeftDevice);

        reset(mTbsService);
        reset(mMcpService);

        // Unbond received in CONNECTION_STATE_CONNECTED
        // Create device descriptor with connect request. To connect service,
        // device needs to be bonded
        doReturn(BluetoothDevice.BOND_BONDED)
                .when(mAdapterService)
                .getBondState(any(BluetoothDevice.class));
        assertWithMessage("Connect failed").that(mService.connect(mLeftDevice)).isTrue();

        generateConnectionMessageFromNative(
                mLeftDevice,
                BluetoothProfile.STATE_CONNECTING,
                BluetoothProfile.STATE_DISCONNECTED);
        generateConnectionMessageFromNative(
                mLeftDevice, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_CONNECTING);
        assertThat(mService.getConnectionState(mLeftDevice))
                .isEqualTo(BluetoothProfile.STATE_CONNECTED);

        assertThat(mService.getDevices().contains(mLeftDevice)).isTrue();

        // Device unbond
        doReturn(BluetoothDevice.BOND_NONE)
                .when(mAdapterService)
                .getBondState(any(BluetoothDevice.class));
        mService.bondStateChanged(mLeftDevice, BluetoothDevice.BOND_NONE);

        assertThat(mService.getDevices().contains(mLeftDevice)).isTrue();
        verifyConnectionStateIntent(
                TIMEOUT_MS,
                mLeftDevice,
                BluetoothProfile.STATE_DISCONNECTING,
                BluetoothProfile.STATE_CONNECTED);
        assertThat(mService.getConnectionState(mLeftDevice))
                .isEqualTo(BluetoothProfile.STATE_DISCONNECTING);
        assertThat(mService.getDevices().contains(mLeftDevice)).isTrue();
        verify(mTbsService, times(0)).removeDeviceAuthorizationInfo(mLeftDevice);
        verify(mMcpService, times(0)).removeDeviceAuthorizationInfo(mLeftDevice);

        reset(mTbsService);
        reset(mMcpService);

        // Inject CONNECTION_STATE_DISCONNECTED
        generateConnectionMessageFromNative(
                mLeftDevice,
                BluetoothProfile.STATE_DISCONNECTED,
                BluetoothProfile.STATE_DISCONNECTING);

        verify(mTbsService, times(1)).removeDeviceAuthorizationInfo(mLeftDevice);
        verify(mMcpService, times(1)).removeDeviceAuthorizationInfo(mLeftDevice);

        reset(mTbsService);
        reset(mMcpService);

        // Unbond received in CONNECTION_STATE_DISCONNECTED
        // Create device descriptor with connect request. To connect service,
        // device needs to be bonded
        doReturn(BluetoothDevice.BOND_BONDED)
                .when(mAdapterService)
                .getBondState(any(BluetoothDevice.class));
        assertWithMessage("Connect failed").that(mService.connect(mLeftDevice)).isTrue();

        generateConnectionMessageFromNative(
                mLeftDevice,
                BluetoothProfile.STATE_CONNECTING,
                BluetoothProfile.STATE_DISCONNECTED);
        generateConnectionMessageFromNative(
                mLeftDevice, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_CONNECTING);
        assertThat(mService.getConnectionState(mLeftDevice))
                .isEqualTo(BluetoothProfile.STATE_CONNECTED);

        assertThat(mService.getDevices().contains(mLeftDevice)).isTrue();
        injectAndVerifyDeviceDisconnected(mLeftDevice);

        verify(mTbsService, times(0)).removeDeviceAuthorizationInfo(mLeftDevice);
        verify(mMcpService, times(0)).removeDeviceAuthorizationInfo(mLeftDevice);

        reset(mTbsService);
        reset(mMcpService);

        // Device unbond
        doReturn(BluetoothDevice.BOND_NONE)
                .when(mAdapterService)
                .getBondState(any(BluetoothDevice.class));
        mService.bondStateChanged(mLeftDevice, BluetoothDevice.BOND_NONE);

        verify(mTbsService, times(1)).removeDeviceAuthorizationInfo(mLeftDevice);
        verify(mMcpService, times(1)).removeDeviceAuthorizationInfo(mLeftDevice);
    }

    /**
     * Test that a CONNECTION_STATE_DISCONNECTED Le Audio stack event will remove the state
     * machine only if the device is unbond.