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

Commit 66ee8345 authored by Treehugger Robot's avatar Treehugger Robot Committed by Automerger Merge Worker
Browse files

Merge "Retry SDP when we receive an SDP_BUSY response" into main am:...

Merge "Retry SDP when we receive an SDP_BUSY response" into main am: 539d963e am: b12c1e79 am: 9aa67096 am: acb3c643

Original change: https://android-review.googlesource.com/c/platform/packages/modules/Bluetooth/+/2762625



Change-Id: I0c22a98c3fb97d348d13c88e7354022590419b8d
Signed-off-by: default avatarAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
parents 300c8a6d acb3c643
Loading
Loading
Loading
Loading
+4 −9
Original line number Diff line number Diff line
@@ -768,19 +768,14 @@ public class MapClientService extends ProfileService {
                }

                if (uuid.equals(BluetoothUuid.MAS)) {
                    // Check if we have a valid SDP record.
                    // Check if we have a successful status with a valid SDP record.
                    int status = intent.getIntExtra(BluetoothDevice.EXTRA_SDP_SEARCH_STATUS, -1);
                    SdpMasRecord masRecord =
                            intent.getParcelableExtra(BluetoothDevice.EXTRA_SDP_RECORD);
                    if (DBG) {
                        Log.d(TAG, "SDP = " + masRecord);
                    }
                    int status = intent.getIntExtra(BluetoothDevice.EXTRA_SDP_SEARCH_STATUS, -1);
                    if (masRecord == null) {
                        Log.w(TAG, "SDP search ended with no MAS record. Status: " + status);
                        return;
                        Log.d(TAG, "SDP complete, status: " + status + ", record:" + masRecord);
                    }
                    stateMachine.obtainMessage(MceStateMachine.MSG_MAS_SDP_DONE,
                            masRecord).sendToTarget();
                    stateMachine.sendSdpResult(status, masRecord);
                }
            }
        }
+47 −4
Original line number Diff line number Diff line
@@ -94,7 +94,7 @@ class MceStateMachine extends StateMachine {
    static final int MSG_MAS_REQUEST_COMPLETED = 1003;
    static final int MSG_MAS_REQUEST_FAILED = 1004;
    static final int MSG_MAS_SDP_DONE = 1005;
    static final int MSG_MAS_SDP_FAILED = 1006;
    static final int MSG_MAS_SDP_UNSUCCESSFUL = 1006;
    static final int MSG_OUTBOUND_MESSAGE = 2001;
    static final int MSG_INBOUND_MESSAGE = 2002;
    static final int MSG_NOTIFICATION = 2003;
@@ -116,9 +116,15 @@ class MceStateMachine extends StateMachine {
    private static final int MAX_MESSAGES = 20;
    private static final int MSG_CONNECT = 1;
    private static final int MSG_DISCONNECT = 2;
    private static final int MSG_CONNECTING_TIMEOUT = 3;
    static final int MSG_CONNECTING_TIMEOUT = 3;
    private static final int MSG_DISCONNECTING_TIMEOUT = 4;

    // Constants for SDP. Note that these values come from the native stack, but no centralized
    // constants exist for them as part of the various SDP APIs.
    public static final int SDP_SUCCESS = 0;
    public static final int SDP_FAILED = 1;
    public static final int SDP_BUSY = 2;

    private static final boolean MESSAGE_SEEN = true;
    private static final boolean MESSAGE_NOT_SEEN = false;

@@ -293,6 +299,21 @@ class MceStateMachine extends StateMachine {
        return mMostRecentState;
    }

    /**
     * Notify of SDP completion.
     */
    public void sendSdpResult(int status, SdpMasRecord record) {
        if (DBG) {
            Log.d(TAG, "Received SDP Result, status=" + status + ", record=" + record);
        }
        if (status != SDP_SUCCESS || record == null) {
            Log.w(TAG, "SDP unsuccessful, status: " + status + ", record: " + record);
            sendMessage(MceStateMachine.MSG_MAS_SDP_UNSUCCESSFUL, status);
            return;
        }
        sendMessage(MceStateMachine.MSG_MAS_SDP_DONE, record);
    }

    public boolean disconnect() {
        if (DBG) {
            Log.d(TAG, "Disconnect Request " + mDevice);
@@ -533,6 +554,28 @@ class MceStateMachine extends StateMachine {
                    }
                    break;

                case MSG_MAS_SDP_UNSUCCESSFUL:
                    int sdpStatus = message.arg1;
                    Log.i(TAG, Utils.getLoggableAddress(mDevice)
                            + " [Connecting]: SDP unsuccessful, status=" + sdpStatus);
                    if (sdpStatus == SDP_BUSY) {
                        if (DBG) {
                            Log.d(TAG, Utils.getLoggableAddress(mDevice)
                                    + " [Connecting]: SDP was busy, try again");
                        }
                        mDevice.sdpSearch(BluetoothUuid.MAS);
                    } else {
                        // This means the status is 0 (success, but no record) or 1 (organic
                        // failure). We historically have never retried SDP in failure cases, so we
                        // don't need to wait for the timeout anymore.
                        if (DBG) {
                            Log.d(TAG, Utils.getLoggableAddress(mDevice)
                                    + " [Connecting]: SDP failed completely, disconnecting");
                        }
                        transitionTo(mDisconnecting);
                    }
                    break;

                case MSG_MAS_CONNECTED:
                    transitionTo(mConnected);
                    break;
@@ -1178,8 +1221,8 @@ class MceStateMachine extends StateMachine {
                return "MSG_MAS_REQUEST_FAILED";
            case MSG_MAS_SDP_DONE:
                return "MSG_MAS_SDP_DONE";
            case MSG_MAS_SDP_FAILED:
                return "MSG_MAS_SDP_FAILED";
            case MSG_MAS_SDP_UNSUCCESSFUL:
                return "MSG_MAS_SDP_UNSUCCESSFUL";
            case MSG_OUTBOUND_MESSAGE:
                return "MSG_OUTBOUND_MESSAGE";
            case MSG_INBOUND_MESSAGE:
+54 −3
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ import static com.google.common.truth.Truth.assertThat;

import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -28,6 +29,7 @@ import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothUuid;
import android.bluetooth.SdpMasRecord;
import android.content.Intent;

import androidx.test.filters.MediumTest;
@@ -322,15 +324,64 @@ public class MapClientServiceTest {
    }

    @Test
    public void broadcastReceiver_withActionSdpRecord_withoutMasRecord_doesNothing() {
    public void broadcastReceiver_withActionSdpRecord_receivedMasRecord_sdpSuccess() {
        MceStateMachine sm = mock(MceStateMachine.class);
        mService.getInstanceMap().put(mRemoteDevice, sm);
        SdpMasRecord mockSdpRecord = mock(SdpMasRecord.class);

        Intent intent = new Intent(BluetoothDevice.ACTION_SDP_RECORD);
        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
        intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_DISCONNECTED);
        intent.putExtra(BluetoothDevice.EXTRA_UUID, BluetoothUuid.MAS);
        // No MasRecord / searchStatus is included in this intent
        intent.putExtra(BluetoothDevice.EXTRA_SDP_SEARCH_STATUS, MceStateMachine.SDP_SUCCESS);
        intent.putExtra(BluetoothDevice.EXTRA_SDP_RECORD, mockSdpRecord);
        mService.mMapReceiver.onReceive(mService, intent);

        verify(sm).sendSdpResult(eq(MceStateMachine.SDP_SUCCESS), eq(mockSdpRecord));
    }

    @Test
    public void broadcastReceiver_withActionSdpRecord_withoutMasRecord_sdpFailed() {
        MceStateMachine sm = mock(MceStateMachine.class);
        mService.getInstanceMap().put(mRemoteDevice, sm);

        Intent intent = new Intent(BluetoothDevice.ACTION_SDP_RECORD);
        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
        intent.putExtra(BluetoothDevice.EXTRA_UUID, BluetoothUuid.MAS);
        intent.putExtra(BluetoothDevice.EXTRA_SDP_SEARCH_STATUS, MceStateMachine.SDP_SUCCESS);
        // No MasRecord is included in this intent
        mService.mMapReceiver.onReceive(mService, intent);

        // Verify message: SDP was successfully complete, but no record was returned
        verify(sm).sendSdpResult(eq(MceStateMachine.SDP_SUCCESS), eq(null));
    }

    @Test
    public void broadcastReceiver_withActionSdpRecord_withSdpBusy_sdpFailed() {
        MceStateMachine sm = mock(MceStateMachine.class);
        mService.getInstanceMap().put(mRemoteDevice, sm);

        Intent intent = new Intent(BluetoothDevice.ACTION_SDP_RECORD);
        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
        intent.putExtra(BluetoothDevice.EXTRA_UUID, BluetoothUuid.MAS);
        intent.putExtra(BluetoothDevice.EXTRA_SDP_SEARCH_STATUS, MceStateMachine.SDP_BUSY);
        mService.mMapReceiver.onReceive(mService, intent);

        // Verify message: SDP was busy and no record was returned
        verify(sm).sendSdpResult(eq(MceStateMachine.SDP_BUSY), eq(null));
    }

    @Test
    public void broadcastReceiver_withActionSdpRecord_withSdpFailed_sdpFailed() {
        MceStateMachine sm = mock(MceStateMachine.class);
        mService.getInstanceMap().put(mRemoteDevice, sm);

        Intent intent = new Intent(BluetoothDevice.ACTION_SDP_RECORD);
        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
        intent.putExtra(BluetoothDevice.EXTRA_UUID, BluetoothUuid.MAS);
        intent.putExtra(BluetoothDevice.EXTRA_SDP_SEARCH_STATUS, MceStateMachine.SDP_FAILED);
        mService.mMapReceiver.onReceive(mService, intent);

        // Verify message: SDP was failed for some reason and no record was returned
        verify(sm).sendSdpResult(eq(MceStateMachine.SDP_FAILED), eq(null));
    }
}
+100 −2
Original line number Diff line number Diff line
@@ -847,6 +847,105 @@ public class MapClientStateMachineTest {
        Assert.assertNull(mIntentArgument.getValue().getPackage());
    }

    @Test
    public void testSdpBusyWhileConnecting_sdpRetried() {
        // Perform first part of MAP connection logic.
        verify(mMockMapClientService,
                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendBroadcastMultiplePermissions(
                mIntentArgument.capture(), any(String[].class),
                any(BroadcastOptions.class));
        Assert.assertEquals(BluetoothProfile.STATE_CONNECTING, mMceStateMachine.getState());

        // Send SDP Failed with status "busy"
        // Note: There's no way to validate the BluetoothDevice#sdpSearch call
        mMceStateMachine.sendSdpResult(MceStateMachine.SDP_BUSY, null);

        // Send successful SDP record, then send MAS Client connected
        SdpMasRecord record = new SdpMasRecord(1, 1, 1, 1, 1, 1, "MasRecord");
        mMceStateMachine.sendSdpResult(MceStateMachine.SDP_SUCCESS, record);
        Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED);
        mMceStateMachine.sendMessage(msg);

        // Verify we move into the connected state
        verify(mMockMapClientService,
                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcastMultiplePermissions(
                mIntentArgument.capture(), any(String[].class),
                any(BroadcastOptions.class));
        assertThat(mMceStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED);
    }

    @Test
    public void testSdpBusyWhileConnectingAndRetryResultsReceivedAfterTimeout_resultsIgnored() {
        // Perform first part of MAP connection logic.
        verify(mMockMapClientService,
                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendBroadcastMultiplePermissions(
                mIntentArgument.capture(), any(String[].class),
                any(BroadcastOptions.class));
        Assert.assertEquals(BluetoothProfile.STATE_CONNECTING, mMceStateMachine.getState());

        // Send SDP Failed with status "busy"
        // Note: There's no way to validate the BluetoothDevice#sdpSearch call
        mMceStateMachine.sendSdpResult(MceStateMachine.SDP_BUSY, null);

        // Timeout waiting for record
        Message msg = Message.obtain(mHandler, MceStateMachine.MSG_CONNECTING_TIMEOUT);
        mMceStateMachine.sendMessage(msg);

        // Verify we move into the disconnecting state
        verify(mMockMapClientService,
                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcastMultiplePermissions(
                mIntentArgument.capture(), any(String[].class),
                any(BroadcastOptions.class));
        assertThat(mMceStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTING);


        // Send successful SDP record, then send MAS Client connected
        SdpMasRecord record = new SdpMasRecord(1, 1, 1, 1, 1, 1, "MasRecord");
        mMceStateMachine.sendSdpResult(MceStateMachine.SDP_SUCCESS, record);

        // Verify nothing happens
        verifyNoMoreInteractions(mMockMapClientService);
    }

    @Test
    public void testSdpFailedWithNoRecordWhileConnecting_deviceDisconnected() {
        // Perform first part of MAP connection logic.
        verify(mMockMapClientService,
                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendBroadcastMultiplePermissions(
                mIntentArgument.capture(), any(String[].class),
                any(BroadcastOptions.class));
        Assert.assertEquals(BluetoothProfile.STATE_CONNECTING, mMceStateMachine.getState());

        // Send SDP process success with no record found
        mMceStateMachine.sendSdpResult(MceStateMachine.SDP_SUCCESS, null);

        // Verify we move into the disconnecting state
        verify(mMockMapClientService,
                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcastMultiplePermissions(
                mIntentArgument.capture(), any(String[].class),
                any(BroadcastOptions.class));
        assertThat(mMceStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTING);
    }

    @Test
    public void testSdpOrganicFailure_deviceDisconnected() {
        // Perform first part of MAP connection logic.
        verify(mMockMapClientService,
                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendBroadcastMultiplePermissions(
                mIntentArgument.capture(), any(String[].class),
                any(BroadcastOptions.class));
        Assert.assertEquals(BluetoothProfile.STATE_CONNECTING, mMceStateMachine.getState());

        // Send SDP Failed entirely
        mMceStateMachine.sendSdpResult(MceStateMachine.SDP_FAILED, null);

        verify(mMockMapClientService,
                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcastMultiplePermissions(
                mIntentArgument.capture(), any(String[].class),
                any(BroadcastOptions.class));
        assertThat(mMceStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTING);
    }

    private void setupSdpRecordReceipt() {
        // Perform first part of MAP connection logic.
        verify(mMockMapClientService,
@@ -857,8 +956,7 @@ public class MapClientStateMachineTest {

        // Setup receipt of SDP record
        SdpMasRecord record = new SdpMasRecord(1, 1, 1, 1, 1, 1, "MasRecord");
        Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_SDP_DONE, record);
        mMceStateMachine.sendMessage(msg);
        mMceStateMachine.sendSdpResult(MceStateMachine.SDP_SUCCESS, record);
    }

    private class MockSmsContentProvider extends MockContentProvider {