Loading android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java +7 −110 Original line number Diff line number Diff line Loading @@ -14,46 +14,6 @@ * limitations under the License. */ /** * Bluetooth Bassclient StateMachine. There is one instance per remote device. * - "Disconnected" and "Connected" are steady states. * - "Connecting" and "Disconnecting" are transient states until the * connection / disconnection is completed. * - "ConnectedProcessing" is an intermediate state to ensure, there is only * one Gatt transaction from the profile at any point of time * * * (Disconnected) * | ^ * CONNECT | | DISCONNECTED * V | * (Connecting)<--->(Disconnecting) * | ^ * CONNECTED | | DISCONNECT * V | * (Connected) * | ^ * GATT_TXN | | GATT_TXN_DONE/GATT_TXN_TIMEOUT * V | * (ConnectedProcessing) * NOTES: * - If state machine is in "Connecting" state and the remote device sends * DISCONNECT request, the state machine transitions to "Disconnecting" state. * - Similarly, if the state machine is in "Disconnecting" state and the remote device * sends CONNECT request, the state machine transitions to "Connecting" state. * - Whenever there is any Gatt Write/read, State machine will moved "ConnectedProcessing" and * all other requests (add, update, remove source) operations will be deferred in * "ConnectedProcessing" state * - Once the gatt transaction is done (or after a specified timeout of no response), * State machine will move back to "Connected" and try to process the deferred requests * as needed * * DISCONNECT * (Connecting) ---------------> (Disconnecting) * <--------------- * CONNECT * */ package com.android.bluetooth.bass_client; import static android.Manifest.permission.BLUETOOTH_CONNECT; Loading Loading @@ -153,7 +113,6 @@ public class BassClientStateMachine extends StateMachine { private final Disconnected mDisconnected = new Disconnected(); private final Connected mConnected = new Connected(); private final Connecting mConnecting = new Connecting(); private final Disconnecting mDisconnecting = new Disconnecting(); private final ConnectedProcessing mConnectedProcessing = new ConnectedProcessing(); @VisibleForTesting final List<BluetoothGattCharacteristic> mBroadcastCharacteristics = Loading @@ -176,8 +135,10 @@ public class BassClientStateMachine extends StateMachine { private BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); private ServiceFactory mFactory = new ServiceFactory(); private int mPendingOperation = -1; private byte mPendingSourceId = -1; @VisibleForTesting int mPendingOperation = -1; @VisibleForTesting byte mPendingSourceId = -1; private BluetoothLeBroadcastMetadata mPendingMetadata = null; private BluetoothLeBroadcastReceiveState mSetBroadcastPINRcvState = null; private boolean mSetBroadcastCodePending = false; Loading @@ -185,7 +146,8 @@ public class BassClientStateMachine extends StateMachine { // Psync and PAST interfaces private PeriodicAdvertisingManager mPeriodicAdvManager; private boolean mAutoAssist = false; private boolean mAutoTriggered = false; @VisibleForTesting boolean mAutoTriggered = false; @VisibleForTesting boolean mNoStopScanOffload = false; private boolean mDefNoPAS = false; Loading @@ -204,7 +166,6 @@ public class BassClientStateMachine extends StateMachine { mService = svc; mConnectTimeoutMs = connectTimeoutMs; addState(mDisconnected); addState(mDisconnecting); addState(mConnected); addState(mConnecting); addState(mConnectedProcessing); Loading Loading @@ -1804,7 +1765,7 @@ public class BassClientStateMachine extends StateMachine { transitionTo(mConnected); break; case GATT_TXN_TIMEOUT: log("GATT transaction timedout for" + mDevice); log("GATT transaction timeout for" + mDevice); sendPendingCallbacks( mPendingOperation, BluetoothStatusCodes.ERROR_UNKNOWN); Loading @@ -1830,67 +1791,6 @@ public class BassClientStateMachine extends StateMachine { } } @VisibleForTesting class Disconnecting extends State { @Override public void enter() { log("Enter Disconnecting(" + mDevice + "): " + messageWhatToString(getCurrentMessage().what)); sendMessageDelayed(CONNECT_TIMEOUT, mDevice, mConnectTimeoutMs); broadcastConnectionState( mDevice, mLastConnectionState, BluetoothProfile.STATE_DISCONNECTING); } @Override public void exit() { log("Exit Disconnecting(" + mDevice + "): " + messageWhatToString(getCurrentMessage().what)); removeMessages(CONNECT_TIMEOUT); mLastConnectionState = BluetoothProfile.STATE_DISCONNECTING; } @Override public boolean processMessage(Message message) { log("Disconnecting process message(" + mDevice + "): " + messageWhatToString(message.what)); switch (message.what) { case CONNECT: log("Disconnecting to " + mDevice); log("deferring this connection request " + mDevice); deferMessage(message); break; case DISCONNECT: Log.w(TAG, "Already disconnecting: DISCONNECT ignored: " + mDevice); break; case CONNECTION_STATE_CHANGED: int state = (int) message.obj; Log.w(TAG, "Disconnecting: connection state changed:" + state); if (state == BluetoothProfile.STATE_CONNECTED) { Log.e(TAG, "should never happen from this state"); transitionTo(mConnected); } else { Log.w(TAG, "disconnection successful to " + mDevice); cancelActiveSync(null); transitionTo(mDisconnected); } break; case CONNECT_TIMEOUT: Log.w(TAG, "CONNECT_TIMEOUT"); BluetoothDevice device = (BluetoothDevice) message.obj; if (!mDevice.equals(device)) { Log.e(TAG, "Unknown device timeout " + device); break; } transitionTo(mDisconnected); break; default: log("Disconnecting: not handled message:" + message.what); return NOT_HANDLED; } return HANDLED; } } void broadcastConnectionState(BluetoothDevice device, int fromState, int toState) { log("broadcastConnectionState " + device + ": " + fromState + "->" + toState); if (fromState == BluetoothProfile.STATE_CONNECTED Loading @@ -1917,9 +1817,6 @@ public class BassClientStateMachine extends StateMachine { case "Disconnected": log("Disconnected"); return BluetoothProfile.STATE_DISCONNECTED; case "Disconnecting": log("Disconnecting"); return BluetoothProfile.STATE_DISCONNECTING; case "Connecting": log("Connecting"); return BluetoothProfile.STATE_CONNECTING; Loading android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientStateMachineTest.java +223 −6 Original line number Diff line number Diff line Loading @@ -16,18 +16,26 @@ package com.android.bluetooth.bass_client; import static android.bluetooth.BluetoothGatt.GATT_FAILURE; import static android.bluetooth.BluetoothGatt.GATT_SUCCESS; import static com.android.bluetooth.bass_client.BassClientStateMachine.ADD_BCAST_SOURCE; import static com.android.bluetooth.bass_client.BassClientStateMachine.CONNECT; import static com.android.bluetooth.bass_client.BassClientStateMachine.CONNECTION_STATE_CHANGED; import static com.android.bluetooth.bass_client.BassClientStateMachine.CONNECT_TIMEOUT; import static com.android.bluetooth.bass_client.BassClientStateMachine.DISCONNECT; import static com.android.bluetooth.bass_client.BassClientStateMachine.GATT_TXN_PROCESSED; import static com.android.bluetooth.bass_client.BassClientStateMachine.GATT_TXN_TIMEOUT; import static com.android.bluetooth.bass_client.BassClientStateMachine.PSYNC_ACTIVE_TIMEOUT; import static com.android.bluetooth.bass_client.BassClientStateMachine.READ_BASS_CHARACTERISTICS; import static com.android.bluetooth.bass_client.BassClientStateMachine.REMOTE_SCAN_START; import static com.android.bluetooth.bass_client.BassClientStateMachine.REMOTE_SCAN_STOP; import static com.android.bluetooth.bass_client.BassClientStateMachine.REMOVE_BCAST_SOURCE; import static com.android.bluetooth.bass_client.BassClientStateMachine.SELECT_BCAST_SOURCE; import static com.android.bluetooth.bass_client.BassClientStateMachine.SET_BCAST_CODE; import static com.android.bluetooth.bass_client.BassClientStateMachine.START_SCAN_OFFLOAD; import static com.android.bluetooth.bass_client.BassClientStateMachine.STOP_SCAN_OFFLOAD; import static com.android.bluetooth.bass_client.BassClientStateMachine.UPDATE_BCAST_SOURCE; import static com.google.common.truth.Truth.assertThat; Loading Loading @@ -313,7 +321,6 @@ public class BassClientStateMachineTest { // connected ----READ_BASS_CHARACTERISTICS---> connectedProcessing --GATT_TXN_PROCESSED // --> connected // Make bluetoothGatt non-null so state will transit mBassClientStateMachine.mBluetoothGatt = Mockito.mock( BassClientStateMachine.BluetoothGattTestableWrapper.class); Loading @@ -330,7 +337,7 @@ public class BassClientStateMachineTest { BluetoothGattCharacteristic.PERMISSION_READ)), BassClientStateMachine.ConnectedProcessing.class); sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(BassClientStateMachine.GATT_TXN_PROCESSED), mBassClientStateMachine.obtainMessage(GATT_TXN_PROCESSED), BassClientStateMachine.Connected.class); // connected ----READ_BASS_CHARACTERISTICS---> connectedProcessing --GATT_TXN_TIMEOUT --> Loading @@ -343,7 +350,7 @@ public class BassClientStateMachineTest { BluetoothGattCharacteristic.PERMISSION_READ)), BassClientStateMachine.ConnectedProcessing.class); sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(BassClientStateMachine.GATT_TXN_TIMEOUT), mBassClientStateMachine.obtainMessage(GATT_TXN_TIMEOUT), BassClientStateMachine.Connected.class); // connected ----START_SCAN_OFFLOAD---> connectedProcessing --GATT_TXN_PROCESSED--> Loading @@ -352,7 +359,7 @@ public class BassClientStateMachineTest { mBassClientStateMachine.obtainMessage(BassClientStateMachine.START_SCAN_OFFLOAD), BassClientStateMachine.ConnectedProcessing.class); sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(BassClientStateMachine.GATT_TXN_PROCESSED), mBassClientStateMachine.obtainMessage(GATT_TXN_PROCESSED), BassClientStateMachine.Connected.class); // connected ----STOP_SCAN_OFFLOAD---> connectedProcessing --GATT_TXN_PROCESSED--> connected Loading @@ -360,7 +367,7 @@ public class BassClientStateMachineTest { mBassClientStateMachine.obtainMessage(STOP_SCAN_OFFLOAD), BassClientStateMachine.ConnectedProcessing.class); sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(BassClientStateMachine.GATT_TXN_PROCESSED), mBassClientStateMachine.obtainMessage(GATT_TXN_PROCESSED), BassClientStateMachine.Connected.class); } Loading Loading @@ -491,7 +498,8 @@ public class BassClientStateMachineTest { mBassClientStateMachine.mBluetoothGatt = btGatt; mBassClientStateMachine.sendMessage(DISCONNECT); verify(btGatt, timeout(TIMEOUT_MS)).disconnect(); TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); verify(btGatt).disconnect(); verify(btGatt).close(); } Loading Loading @@ -733,6 +741,198 @@ public class BassClientStateMachineTest { // TODO: add sendMessage tests for BCAST related messages @Test public void sendConnectMessage_inConnectedProcessingState_doesNotChangeState() { initToConnectedProcessingState(); mBassClientStateMachine.sendMessage(CONNECT); TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); verify(mBassClientService, never()).sendBroadcast(any(Intent.class), anyString(), any()); } @Test public void sendDisconnectMessage_inConnectedProcessingState_doesNotChangeState() { initToConnectedProcessingState(); // Mock instance of btGatt was created in initToConnectedProcessingState(). BassClientStateMachine.BluetoothGattTestableWrapper btGatt = mBassClientStateMachine.mBluetoothGatt; mBassClientStateMachine.mBluetoothGatt = null; mBassClientStateMachine.sendMessage(DISCONNECT); TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); verify(mBassClientService, never()).sendBroadcast(any(Intent.class), anyString(), any()); mBassClientStateMachine.mBluetoothGatt = btGatt; sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(DISCONNECT), BassClientStateMachine.Disconnected.class); verify(btGatt).disconnect(); verify(btGatt).close(); } @Test public void sendStateChangedMessage_inConnectedProcessingState() { initToConnectedProcessingState(); Message msgToConnectedState = mBassClientStateMachine.obtainMessage(CONNECTION_STATE_CHANGED); msgToConnectedState.obj = BluetoothProfile.STATE_CONNECTED; mBassClientStateMachine.sendMessage(msgToConnectedState); TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); verify(mBassClientService, never()).sendBroadcast(any(Intent.class), anyString(), any()); Message msgToNoneConnectedState = mBassClientStateMachine.obtainMessage(CONNECTION_STATE_CHANGED); msgToNoneConnectedState.obj = BluetoothProfile.STATE_DISCONNECTING; sendMessageAndVerifyTransition( msgToNoneConnectedState, BassClientStateMachine.Disconnected.class); } /** * This also tests BassClientStateMachine#sendPendingCallbacks */ @Test public void sendGattTxnProcessedMessage_inConnectedProcessingState() { initToConnectedProcessingState(); BassClientService.Callbacks callbacks = Mockito.mock(BassClientService.Callbacks.class); when(mBassClientService.getCallbacks()).thenReturn(callbacks); // Test sendPendingCallbacks(START_SCAN_OFFLOAD, ERROR_UNKNOWN) mBassClientStateMachine.mPendingOperation = START_SCAN_OFFLOAD; mBassClientStateMachine.mNoStopScanOffload = true; mBassClientStateMachine.mAutoTriggered = false; sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(GATT_TXN_PROCESSED, GATT_FAILURE), BassClientStateMachine.Connected.class); assertThat(mBassClientStateMachine.mNoStopScanOffload).isFalse(); // Test sendPendingCallbacks(START_SCAN_OFFLOAD, ERROR_UNKNOWN) moveConnectedStateToConnectedProcessingState(); mBassClientStateMachine.mPendingOperation = START_SCAN_OFFLOAD; mBassClientStateMachine.mAutoTriggered = true; sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(GATT_TXN_PROCESSED, GATT_FAILURE), BassClientStateMachine.Connected.class); assertThat(mBassClientStateMachine.mAutoTriggered).isFalse(); // Test sendPendingCallbacks(ADD_BCAST_SOURCE, ERROR_UNKNOWN) moveConnectedStateToConnectedProcessingState(); mBassClientStateMachine.mPendingOperation = ADD_BCAST_SOURCE; sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(GATT_TXN_PROCESSED, GATT_FAILURE), BassClientStateMachine.Connected.class); verify(callbacks).notifySourceAddFailed(any(), any(), anyInt()); // Test sendPendingCallbacks(UPDATE_BCAST_SOURCE, REASON_LOCAL_APP_REQUEST) moveConnectedStateToConnectedProcessingState(); mBassClientStateMachine.mPendingOperation = UPDATE_BCAST_SOURCE; mBassClientStateMachine.mAutoTriggered = true; sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(GATT_TXN_PROCESSED, GATT_SUCCESS), BassClientStateMachine.Connected.class); assertThat(mBassClientStateMachine.mAutoTriggered).isFalse(); // Test sendPendingCallbacks(UPDATE_BCAST_SOURCE, ERROR_UNKNOWN) moveConnectedStateToConnectedProcessingState(); mBassClientStateMachine.mPendingOperation = UPDATE_BCAST_SOURCE; sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(GATT_TXN_PROCESSED, GATT_FAILURE), BassClientStateMachine.Connected.class); verify(callbacks).notifySourceModifyFailed(any(), anyInt(), anyInt()); // Test sendPendingCallbacks(REMOVE_BCAST_SOURCE, ERROR_UNKNOWN) moveConnectedStateToConnectedProcessingState(); mBassClientStateMachine.mPendingOperation = REMOVE_BCAST_SOURCE; sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(GATT_TXN_PROCESSED, GATT_FAILURE), BassClientStateMachine.Connected.class); verify(callbacks).notifySourceRemoveFailed(any(), anyInt(), anyInt()); // Test sendPendingCallbacks(SET_BCAST_CODE, REASON_LOCAL_APP_REQUEST) moveConnectedStateToConnectedProcessingState(); mBassClientStateMachine.mPendingOperation = REMOVE_BCAST_SOURCE; sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(GATT_TXN_PROCESSED, GATT_FAILURE), BassClientStateMachine.Connected.class); // Nothing to verify more // Test sendPendingCallbacks(SET_BCAST_CODE, REASON_LOCAL_APP_REQUEST) moveConnectedStateToConnectedProcessingState(); mBassClientStateMachine.mPendingOperation = -1; sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(GATT_TXN_PROCESSED, GATT_FAILURE), BassClientStateMachine.Connected.class); // Nothing to verify more } @Test public void sendGattTxnTimeoutMessage_inConnectedProcessingState_doesNotChangeState() { initToConnectedProcessingState(); mBassClientStateMachine.mPendingOperation = SET_BCAST_CODE; mBassClientStateMachine.mPendingSourceId = 0; sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(GATT_TXN_TIMEOUT, GATT_FAILURE), BassClientStateMachine.Connected.class); assertThat(mBassClientStateMachine.mPendingOperation).isEqualTo(-1); assertThat(mBassClientStateMachine.mPendingSourceId).isEqualTo(-1); } @Test public void sendMessageForDeferring_inConnectedProcessingState_defersMessage() { initToConnectedProcessingState(); mBassClientStateMachine.sendMessage(READ_BASS_CHARACTERISTICS); TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); assertThat(mBassClientStateMachine.hasDeferredMessagesSuper(READ_BASS_CHARACTERISTICS)) .isTrue(); mBassClientStateMachine.sendMessage(START_SCAN_OFFLOAD); TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); assertThat(mBassClientStateMachine.hasDeferredMessagesSuper(START_SCAN_OFFLOAD)) .isTrue(); mBassClientStateMachine.sendMessage(STOP_SCAN_OFFLOAD); TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); assertThat(mBassClientStateMachine.hasDeferredMessagesSuper(STOP_SCAN_OFFLOAD)) .isTrue(); mBassClientStateMachine.sendMessage(SELECT_BCAST_SOURCE); TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); assertThat(mBassClientStateMachine.hasDeferredMessagesSuper(SELECT_BCAST_SOURCE)) .isTrue(); mBassClientStateMachine.sendMessage(ADD_BCAST_SOURCE); TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); assertThat(mBassClientStateMachine.hasDeferredMessagesSuper(ADD_BCAST_SOURCE)) .isTrue(); mBassClientStateMachine.sendMessage(SET_BCAST_CODE); TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); assertThat(mBassClientStateMachine.hasDeferredMessagesSuper(SET_BCAST_CODE)) .isTrue(); mBassClientStateMachine.sendMessage(REMOVE_BCAST_SOURCE); TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); assertThat(mBassClientStateMachine.hasDeferredMessagesSuper(REMOVE_BCAST_SOURCE)) .isTrue(); mBassClientStateMachine.sendMessage(PSYNC_ACTIVE_TIMEOUT); TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); assertThat(mBassClientStateMachine.hasDeferredMessagesSuper(PSYNC_ACTIVE_TIMEOUT)) .isTrue(); } @Test public void sendInvalidMessage_inConnectedProcessingState_doesNotChangeState() { initToConnectedProcessingState(); mBassClientStateMachine.sendMessage(-1); TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); verify(mBassClientService, never()).sendBroadcast(any(Intent.class), anyString(), any()); } private void initToDisconnectedState() { allowConnection(true); allowConnectGatt(true); Loading @@ -758,6 +958,23 @@ public class BassClientStateMachineTest { Mockito.clearInvocations(mBassClientService); } private void initToConnectedProcessingState() { initToConnectedState(); moveConnectedStateToConnectedProcessingState(); } private void moveConnectedStateToConnectedProcessingState() { BluetoothGattCharacteristic gattCharacteristic = Mockito.mock( BluetoothGattCharacteristic.class); BassClientStateMachine.BluetoothGattTestableWrapper btGatt = Mockito.mock( BassClientStateMachine.BluetoothGattTestableWrapper.class); mBassClientStateMachine.mBluetoothGatt = btGatt; sendMessageAndVerifyTransition(mBassClientStateMachine.obtainMessage( READ_BASS_CHARACTERISTICS, gattCharacteristic), BassClientStateMachine.ConnectedProcessing.class); Mockito.clearInvocations(mBassClientService); } private <T> void sendMessageAndVerifyTransition(Message msg, Class<T> type) { Mockito.clearInvocations(mBassClientService); mBassClientStateMachine.sendMessage(msg); Loading Loading
android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java +7 −110 Original line number Diff line number Diff line Loading @@ -14,46 +14,6 @@ * limitations under the License. */ /** * Bluetooth Bassclient StateMachine. There is one instance per remote device. * - "Disconnected" and "Connected" are steady states. * - "Connecting" and "Disconnecting" are transient states until the * connection / disconnection is completed. * - "ConnectedProcessing" is an intermediate state to ensure, there is only * one Gatt transaction from the profile at any point of time * * * (Disconnected) * | ^ * CONNECT | | DISCONNECTED * V | * (Connecting)<--->(Disconnecting) * | ^ * CONNECTED | | DISCONNECT * V | * (Connected) * | ^ * GATT_TXN | | GATT_TXN_DONE/GATT_TXN_TIMEOUT * V | * (ConnectedProcessing) * NOTES: * - If state machine is in "Connecting" state and the remote device sends * DISCONNECT request, the state machine transitions to "Disconnecting" state. * - Similarly, if the state machine is in "Disconnecting" state and the remote device * sends CONNECT request, the state machine transitions to "Connecting" state. * - Whenever there is any Gatt Write/read, State machine will moved "ConnectedProcessing" and * all other requests (add, update, remove source) operations will be deferred in * "ConnectedProcessing" state * - Once the gatt transaction is done (or after a specified timeout of no response), * State machine will move back to "Connected" and try to process the deferred requests * as needed * * DISCONNECT * (Connecting) ---------------> (Disconnecting) * <--------------- * CONNECT * */ package com.android.bluetooth.bass_client; import static android.Manifest.permission.BLUETOOTH_CONNECT; Loading Loading @@ -153,7 +113,6 @@ public class BassClientStateMachine extends StateMachine { private final Disconnected mDisconnected = new Disconnected(); private final Connected mConnected = new Connected(); private final Connecting mConnecting = new Connecting(); private final Disconnecting mDisconnecting = new Disconnecting(); private final ConnectedProcessing mConnectedProcessing = new ConnectedProcessing(); @VisibleForTesting final List<BluetoothGattCharacteristic> mBroadcastCharacteristics = Loading @@ -176,8 +135,10 @@ public class BassClientStateMachine extends StateMachine { private BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); private ServiceFactory mFactory = new ServiceFactory(); private int mPendingOperation = -1; private byte mPendingSourceId = -1; @VisibleForTesting int mPendingOperation = -1; @VisibleForTesting byte mPendingSourceId = -1; private BluetoothLeBroadcastMetadata mPendingMetadata = null; private BluetoothLeBroadcastReceiveState mSetBroadcastPINRcvState = null; private boolean mSetBroadcastCodePending = false; Loading @@ -185,7 +146,8 @@ public class BassClientStateMachine extends StateMachine { // Psync and PAST interfaces private PeriodicAdvertisingManager mPeriodicAdvManager; private boolean mAutoAssist = false; private boolean mAutoTriggered = false; @VisibleForTesting boolean mAutoTriggered = false; @VisibleForTesting boolean mNoStopScanOffload = false; private boolean mDefNoPAS = false; Loading @@ -204,7 +166,6 @@ public class BassClientStateMachine extends StateMachine { mService = svc; mConnectTimeoutMs = connectTimeoutMs; addState(mDisconnected); addState(mDisconnecting); addState(mConnected); addState(mConnecting); addState(mConnectedProcessing); Loading Loading @@ -1804,7 +1765,7 @@ public class BassClientStateMachine extends StateMachine { transitionTo(mConnected); break; case GATT_TXN_TIMEOUT: log("GATT transaction timedout for" + mDevice); log("GATT transaction timeout for" + mDevice); sendPendingCallbacks( mPendingOperation, BluetoothStatusCodes.ERROR_UNKNOWN); Loading @@ -1830,67 +1791,6 @@ public class BassClientStateMachine extends StateMachine { } } @VisibleForTesting class Disconnecting extends State { @Override public void enter() { log("Enter Disconnecting(" + mDevice + "): " + messageWhatToString(getCurrentMessage().what)); sendMessageDelayed(CONNECT_TIMEOUT, mDevice, mConnectTimeoutMs); broadcastConnectionState( mDevice, mLastConnectionState, BluetoothProfile.STATE_DISCONNECTING); } @Override public void exit() { log("Exit Disconnecting(" + mDevice + "): " + messageWhatToString(getCurrentMessage().what)); removeMessages(CONNECT_TIMEOUT); mLastConnectionState = BluetoothProfile.STATE_DISCONNECTING; } @Override public boolean processMessage(Message message) { log("Disconnecting process message(" + mDevice + "): " + messageWhatToString(message.what)); switch (message.what) { case CONNECT: log("Disconnecting to " + mDevice); log("deferring this connection request " + mDevice); deferMessage(message); break; case DISCONNECT: Log.w(TAG, "Already disconnecting: DISCONNECT ignored: " + mDevice); break; case CONNECTION_STATE_CHANGED: int state = (int) message.obj; Log.w(TAG, "Disconnecting: connection state changed:" + state); if (state == BluetoothProfile.STATE_CONNECTED) { Log.e(TAG, "should never happen from this state"); transitionTo(mConnected); } else { Log.w(TAG, "disconnection successful to " + mDevice); cancelActiveSync(null); transitionTo(mDisconnected); } break; case CONNECT_TIMEOUT: Log.w(TAG, "CONNECT_TIMEOUT"); BluetoothDevice device = (BluetoothDevice) message.obj; if (!mDevice.equals(device)) { Log.e(TAG, "Unknown device timeout " + device); break; } transitionTo(mDisconnected); break; default: log("Disconnecting: not handled message:" + message.what); return NOT_HANDLED; } return HANDLED; } } void broadcastConnectionState(BluetoothDevice device, int fromState, int toState) { log("broadcastConnectionState " + device + ": " + fromState + "->" + toState); if (fromState == BluetoothProfile.STATE_CONNECTED Loading @@ -1917,9 +1817,6 @@ public class BassClientStateMachine extends StateMachine { case "Disconnected": log("Disconnected"); return BluetoothProfile.STATE_DISCONNECTED; case "Disconnecting": log("Disconnecting"); return BluetoothProfile.STATE_DISCONNECTING; case "Connecting": log("Connecting"); return BluetoothProfile.STATE_CONNECTING; Loading
android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientStateMachineTest.java +223 −6 Original line number Diff line number Diff line Loading @@ -16,18 +16,26 @@ package com.android.bluetooth.bass_client; import static android.bluetooth.BluetoothGatt.GATT_FAILURE; import static android.bluetooth.BluetoothGatt.GATT_SUCCESS; import static com.android.bluetooth.bass_client.BassClientStateMachine.ADD_BCAST_SOURCE; import static com.android.bluetooth.bass_client.BassClientStateMachine.CONNECT; import static com.android.bluetooth.bass_client.BassClientStateMachine.CONNECTION_STATE_CHANGED; import static com.android.bluetooth.bass_client.BassClientStateMachine.CONNECT_TIMEOUT; import static com.android.bluetooth.bass_client.BassClientStateMachine.DISCONNECT; import static com.android.bluetooth.bass_client.BassClientStateMachine.GATT_TXN_PROCESSED; import static com.android.bluetooth.bass_client.BassClientStateMachine.GATT_TXN_TIMEOUT; import static com.android.bluetooth.bass_client.BassClientStateMachine.PSYNC_ACTIVE_TIMEOUT; import static com.android.bluetooth.bass_client.BassClientStateMachine.READ_BASS_CHARACTERISTICS; import static com.android.bluetooth.bass_client.BassClientStateMachine.REMOTE_SCAN_START; import static com.android.bluetooth.bass_client.BassClientStateMachine.REMOTE_SCAN_STOP; import static com.android.bluetooth.bass_client.BassClientStateMachine.REMOVE_BCAST_SOURCE; import static com.android.bluetooth.bass_client.BassClientStateMachine.SELECT_BCAST_SOURCE; import static com.android.bluetooth.bass_client.BassClientStateMachine.SET_BCAST_CODE; import static com.android.bluetooth.bass_client.BassClientStateMachine.START_SCAN_OFFLOAD; import static com.android.bluetooth.bass_client.BassClientStateMachine.STOP_SCAN_OFFLOAD; import static com.android.bluetooth.bass_client.BassClientStateMachine.UPDATE_BCAST_SOURCE; import static com.google.common.truth.Truth.assertThat; Loading Loading @@ -313,7 +321,6 @@ public class BassClientStateMachineTest { // connected ----READ_BASS_CHARACTERISTICS---> connectedProcessing --GATT_TXN_PROCESSED // --> connected // Make bluetoothGatt non-null so state will transit mBassClientStateMachine.mBluetoothGatt = Mockito.mock( BassClientStateMachine.BluetoothGattTestableWrapper.class); Loading @@ -330,7 +337,7 @@ public class BassClientStateMachineTest { BluetoothGattCharacteristic.PERMISSION_READ)), BassClientStateMachine.ConnectedProcessing.class); sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(BassClientStateMachine.GATT_TXN_PROCESSED), mBassClientStateMachine.obtainMessage(GATT_TXN_PROCESSED), BassClientStateMachine.Connected.class); // connected ----READ_BASS_CHARACTERISTICS---> connectedProcessing --GATT_TXN_TIMEOUT --> Loading @@ -343,7 +350,7 @@ public class BassClientStateMachineTest { BluetoothGattCharacteristic.PERMISSION_READ)), BassClientStateMachine.ConnectedProcessing.class); sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(BassClientStateMachine.GATT_TXN_TIMEOUT), mBassClientStateMachine.obtainMessage(GATT_TXN_TIMEOUT), BassClientStateMachine.Connected.class); // connected ----START_SCAN_OFFLOAD---> connectedProcessing --GATT_TXN_PROCESSED--> Loading @@ -352,7 +359,7 @@ public class BassClientStateMachineTest { mBassClientStateMachine.obtainMessage(BassClientStateMachine.START_SCAN_OFFLOAD), BassClientStateMachine.ConnectedProcessing.class); sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(BassClientStateMachine.GATT_TXN_PROCESSED), mBassClientStateMachine.obtainMessage(GATT_TXN_PROCESSED), BassClientStateMachine.Connected.class); // connected ----STOP_SCAN_OFFLOAD---> connectedProcessing --GATT_TXN_PROCESSED--> connected Loading @@ -360,7 +367,7 @@ public class BassClientStateMachineTest { mBassClientStateMachine.obtainMessage(STOP_SCAN_OFFLOAD), BassClientStateMachine.ConnectedProcessing.class); sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(BassClientStateMachine.GATT_TXN_PROCESSED), mBassClientStateMachine.obtainMessage(GATT_TXN_PROCESSED), BassClientStateMachine.Connected.class); } Loading Loading @@ -491,7 +498,8 @@ public class BassClientStateMachineTest { mBassClientStateMachine.mBluetoothGatt = btGatt; mBassClientStateMachine.sendMessage(DISCONNECT); verify(btGatt, timeout(TIMEOUT_MS)).disconnect(); TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); verify(btGatt).disconnect(); verify(btGatt).close(); } Loading Loading @@ -733,6 +741,198 @@ public class BassClientStateMachineTest { // TODO: add sendMessage tests for BCAST related messages @Test public void sendConnectMessage_inConnectedProcessingState_doesNotChangeState() { initToConnectedProcessingState(); mBassClientStateMachine.sendMessage(CONNECT); TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); verify(mBassClientService, never()).sendBroadcast(any(Intent.class), anyString(), any()); } @Test public void sendDisconnectMessage_inConnectedProcessingState_doesNotChangeState() { initToConnectedProcessingState(); // Mock instance of btGatt was created in initToConnectedProcessingState(). BassClientStateMachine.BluetoothGattTestableWrapper btGatt = mBassClientStateMachine.mBluetoothGatt; mBassClientStateMachine.mBluetoothGatt = null; mBassClientStateMachine.sendMessage(DISCONNECT); TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); verify(mBassClientService, never()).sendBroadcast(any(Intent.class), anyString(), any()); mBassClientStateMachine.mBluetoothGatt = btGatt; sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(DISCONNECT), BassClientStateMachine.Disconnected.class); verify(btGatt).disconnect(); verify(btGatt).close(); } @Test public void sendStateChangedMessage_inConnectedProcessingState() { initToConnectedProcessingState(); Message msgToConnectedState = mBassClientStateMachine.obtainMessage(CONNECTION_STATE_CHANGED); msgToConnectedState.obj = BluetoothProfile.STATE_CONNECTED; mBassClientStateMachine.sendMessage(msgToConnectedState); TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); verify(mBassClientService, never()).sendBroadcast(any(Intent.class), anyString(), any()); Message msgToNoneConnectedState = mBassClientStateMachine.obtainMessage(CONNECTION_STATE_CHANGED); msgToNoneConnectedState.obj = BluetoothProfile.STATE_DISCONNECTING; sendMessageAndVerifyTransition( msgToNoneConnectedState, BassClientStateMachine.Disconnected.class); } /** * This also tests BassClientStateMachine#sendPendingCallbacks */ @Test public void sendGattTxnProcessedMessage_inConnectedProcessingState() { initToConnectedProcessingState(); BassClientService.Callbacks callbacks = Mockito.mock(BassClientService.Callbacks.class); when(mBassClientService.getCallbacks()).thenReturn(callbacks); // Test sendPendingCallbacks(START_SCAN_OFFLOAD, ERROR_UNKNOWN) mBassClientStateMachine.mPendingOperation = START_SCAN_OFFLOAD; mBassClientStateMachine.mNoStopScanOffload = true; mBassClientStateMachine.mAutoTriggered = false; sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(GATT_TXN_PROCESSED, GATT_FAILURE), BassClientStateMachine.Connected.class); assertThat(mBassClientStateMachine.mNoStopScanOffload).isFalse(); // Test sendPendingCallbacks(START_SCAN_OFFLOAD, ERROR_UNKNOWN) moveConnectedStateToConnectedProcessingState(); mBassClientStateMachine.mPendingOperation = START_SCAN_OFFLOAD; mBassClientStateMachine.mAutoTriggered = true; sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(GATT_TXN_PROCESSED, GATT_FAILURE), BassClientStateMachine.Connected.class); assertThat(mBassClientStateMachine.mAutoTriggered).isFalse(); // Test sendPendingCallbacks(ADD_BCAST_SOURCE, ERROR_UNKNOWN) moveConnectedStateToConnectedProcessingState(); mBassClientStateMachine.mPendingOperation = ADD_BCAST_SOURCE; sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(GATT_TXN_PROCESSED, GATT_FAILURE), BassClientStateMachine.Connected.class); verify(callbacks).notifySourceAddFailed(any(), any(), anyInt()); // Test sendPendingCallbacks(UPDATE_BCAST_SOURCE, REASON_LOCAL_APP_REQUEST) moveConnectedStateToConnectedProcessingState(); mBassClientStateMachine.mPendingOperation = UPDATE_BCAST_SOURCE; mBassClientStateMachine.mAutoTriggered = true; sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(GATT_TXN_PROCESSED, GATT_SUCCESS), BassClientStateMachine.Connected.class); assertThat(mBassClientStateMachine.mAutoTriggered).isFalse(); // Test sendPendingCallbacks(UPDATE_BCAST_SOURCE, ERROR_UNKNOWN) moveConnectedStateToConnectedProcessingState(); mBassClientStateMachine.mPendingOperation = UPDATE_BCAST_SOURCE; sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(GATT_TXN_PROCESSED, GATT_FAILURE), BassClientStateMachine.Connected.class); verify(callbacks).notifySourceModifyFailed(any(), anyInt(), anyInt()); // Test sendPendingCallbacks(REMOVE_BCAST_SOURCE, ERROR_UNKNOWN) moveConnectedStateToConnectedProcessingState(); mBassClientStateMachine.mPendingOperation = REMOVE_BCAST_SOURCE; sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(GATT_TXN_PROCESSED, GATT_FAILURE), BassClientStateMachine.Connected.class); verify(callbacks).notifySourceRemoveFailed(any(), anyInt(), anyInt()); // Test sendPendingCallbacks(SET_BCAST_CODE, REASON_LOCAL_APP_REQUEST) moveConnectedStateToConnectedProcessingState(); mBassClientStateMachine.mPendingOperation = REMOVE_BCAST_SOURCE; sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(GATT_TXN_PROCESSED, GATT_FAILURE), BassClientStateMachine.Connected.class); // Nothing to verify more // Test sendPendingCallbacks(SET_BCAST_CODE, REASON_LOCAL_APP_REQUEST) moveConnectedStateToConnectedProcessingState(); mBassClientStateMachine.mPendingOperation = -1; sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(GATT_TXN_PROCESSED, GATT_FAILURE), BassClientStateMachine.Connected.class); // Nothing to verify more } @Test public void sendGattTxnTimeoutMessage_inConnectedProcessingState_doesNotChangeState() { initToConnectedProcessingState(); mBassClientStateMachine.mPendingOperation = SET_BCAST_CODE; mBassClientStateMachine.mPendingSourceId = 0; sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(GATT_TXN_TIMEOUT, GATT_FAILURE), BassClientStateMachine.Connected.class); assertThat(mBassClientStateMachine.mPendingOperation).isEqualTo(-1); assertThat(mBassClientStateMachine.mPendingSourceId).isEqualTo(-1); } @Test public void sendMessageForDeferring_inConnectedProcessingState_defersMessage() { initToConnectedProcessingState(); mBassClientStateMachine.sendMessage(READ_BASS_CHARACTERISTICS); TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); assertThat(mBassClientStateMachine.hasDeferredMessagesSuper(READ_BASS_CHARACTERISTICS)) .isTrue(); mBassClientStateMachine.sendMessage(START_SCAN_OFFLOAD); TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); assertThat(mBassClientStateMachine.hasDeferredMessagesSuper(START_SCAN_OFFLOAD)) .isTrue(); mBassClientStateMachine.sendMessage(STOP_SCAN_OFFLOAD); TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); assertThat(mBassClientStateMachine.hasDeferredMessagesSuper(STOP_SCAN_OFFLOAD)) .isTrue(); mBassClientStateMachine.sendMessage(SELECT_BCAST_SOURCE); TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); assertThat(mBassClientStateMachine.hasDeferredMessagesSuper(SELECT_BCAST_SOURCE)) .isTrue(); mBassClientStateMachine.sendMessage(ADD_BCAST_SOURCE); TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); assertThat(mBassClientStateMachine.hasDeferredMessagesSuper(ADD_BCAST_SOURCE)) .isTrue(); mBassClientStateMachine.sendMessage(SET_BCAST_CODE); TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); assertThat(mBassClientStateMachine.hasDeferredMessagesSuper(SET_BCAST_CODE)) .isTrue(); mBassClientStateMachine.sendMessage(REMOVE_BCAST_SOURCE); TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); assertThat(mBassClientStateMachine.hasDeferredMessagesSuper(REMOVE_BCAST_SOURCE)) .isTrue(); mBassClientStateMachine.sendMessage(PSYNC_ACTIVE_TIMEOUT); TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); assertThat(mBassClientStateMachine.hasDeferredMessagesSuper(PSYNC_ACTIVE_TIMEOUT)) .isTrue(); } @Test public void sendInvalidMessage_inConnectedProcessingState_doesNotChangeState() { initToConnectedProcessingState(); mBassClientStateMachine.sendMessage(-1); TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); verify(mBassClientService, never()).sendBroadcast(any(Intent.class), anyString(), any()); } private void initToDisconnectedState() { allowConnection(true); allowConnectGatt(true); Loading @@ -758,6 +958,23 @@ public class BassClientStateMachineTest { Mockito.clearInvocations(mBassClientService); } private void initToConnectedProcessingState() { initToConnectedState(); moveConnectedStateToConnectedProcessingState(); } private void moveConnectedStateToConnectedProcessingState() { BluetoothGattCharacteristic gattCharacteristic = Mockito.mock( BluetoothGattCharacteristic.class); BassClientStateMachine.BluetoothGattTestableWrapper btGatt = Mockito.mock( BassClientStateMachine.BluetoothGattTestableWrapper.class); mBassClientStateMachine.mBluetoothGatt = btGatt; sendMessageAndVerifyTransition(mBassClientStateMachine.obtainMessage( READ_BASS_CHARACTERISTICS, gattCharacteristic), BassClientStateMachine.ConnectedProcessing.class); Mockito.clearInvocations(mBassClientService); } private <T> void sendMessageAndVerifyTransition(Message msg, Class<T> type) { Mockito.clearInvocations(mBassClientService); mBassClientStateMachine.sendMessage(msg); Loading