Loading android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java +104 −6 Original line number Original line Diff line number Diff line Loading @@ -58,6 +58,7 @@ package com.android.bluetooth.bass_client; import static android.Manifest.permission.BLUETOOTH_CONNECT; import static android.Manifest.permission.BLUETOOTH_CONNECT; import android.annotation.Nullable; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGatt; Loading Loading @@ -106,6 +107,7 @@ import java.util.Iterator; import java.util.List; import java.util.List; import java.util.Map; import java.util.Map; import java.util.Scanner; import java.util.Scanner; import java.util.UUID; import java.util.stream.IntStream; import java.util.stream.IntStream; @VisibleForTesting @VisibleForTesting Loading Loading @@ -161,8 +163,8 @@ public class BassClientStateMachine extends StateMachine { private boolean mDiscoveryInitiated = false; private boolean mDiscoveryInitiated = false; @VisibleForTesting @VisibleForTesting BassClientService mService; BassClientService mService; @VisibleForTesting private BluetoothGattCharacteristic mBroadcastScanControlPoint; BluetoothGattCharacteristic mBroadcastScanControlPoint; private boolean mFirstTimeBisDiscovery = false; private boolean mFirstTimeBisDiscovery = false; private int mPASyncRetryCounter = 0; private int mPASyncRetryCounter = 0; private ScanResult mScanRes = null; private ScanResult mScanRes = null; Loading @@ -185,8 +187,8 @@ public class BassClientStateMachine extends StateMachine { private int mBroadcastSourceIdLength = 3; private int mBroadcastSourceIdLength = 3; private byte mNextSourceId = 0; private byte mNextSourceId = 0; private boolean mAllowReconnect = false; private boolean mAllowReconnect = false; @VisibleForTesting BluetoothGatt mBluetoothGatt = null; BluetoothGattTestableWrapper mBluetoothGatt = null; BluetoothGattCallback mGattCallback = null; BluetoothGattCallback mGattCallback = null; BassClientStateMachine(BluetoothDevice device, BassClientService svc, Looper looper, BassClientStateMachine(BluetoothDevice device, BassClientService svc, Looper looper, Loading Loading @@ -1011,11 +1013,16 @@ public class BassClientStateMachine extends StateMachine { mGattCallback = new GattCallback(); mGattCallback = new GattCallback(); } } mBluetoothGatt = mDevice.connectGatt(mService, autoConnect, BluetoothGatt gatt = mDevice.connectGatt(mService, autoConnect, mGattCallback, BluetoothDevice.TRANSPORT_LE, mGattCallback, BluetoothDevice.TRANSPORT_LE, (BluetoothDevice.PHY_LE_1M_MASK (BluetoothDevice.PHY_LE_1M_MASK | BluetoothDevice.PHY_LE_2M_MASK | BluetoothDevice.PHY_LE_2M_MASK | BluetoothDevice.PHY_LE_CODED_MASK), null); | BluetoothDevice.PHY_LE_CODED_MASK), null); if (gatt != null) { mBluetoothGatt = new BluetoothGattTestableWrapper(gatt); } return mBluetoothGatt != null; return mBluetoothGatt != null; } } Loading Loading @@ -1465,6 +1472,12 @@ public class BassClientStateMachine extends StateMachine { removeDeferredMessages(CONNECT); removeDeferredMessages(CONNECT); if (mLastConnectionState == BluetoothProfile.STATE_CONNECTED) { if (mLastConnectionState == BluetoothProfile.STATE_CONNECTED) { log("CONNECTED->CONNECTED: Ignore"); log("CONNECTED->CONNECTED: Ignore"); // Broadcast for testing purpose only if (Utils.isInstrumentationTestMode()) { Intent intent = new Intent("android.bluetooth.bass_client.NOTIFY_TEST"); mService.sendBroadcast(intent, BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions()); } } else { } else { broadcastConnectionState(mDevice, mLastConnectionState, broadcastConnectionState(mDevice, mLastConnectionState, BluetoothProfile.STATE_CONNECTED); BluetoothProfile.STATE_CONNECTED); Loading Loading @@ -1724,12 +1737,20 @@ public class BassClientStateMachine extends StateMachine { } } } } // public for testing, but private for non-testing @VisibleForTesting @VisibleForTesting class ConnectedProcessing extends State { class ConnectedProcessing extends State { @Override @Override public void enter() { public void enter() { log("Enter ConnectedProcessing(" + mDevice + "): " log("Enter ConnectedProcessing(" + mDevice + "): " + messageWhatToString(getCurrentMessage().what)); + messageWhatToString(getCurrentMessage().what)); // Broadcast for testing purpose only if (Utils.isInstrumentationTestMode()) { Intent intent = new Intent("android.bluetooth.bass_client.NOTIFY_TEST"); mService.sendBroadcast(intent, BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions()); } } } @Override @Override public void exit() { public void exit() { Loading Loading @@ -2023,4 +2044,81 @@ public class BassClientStateMachine extends StateMachine { } } Log.d(TAG, builder.toString()); Log.d(TAG, builder.toString()); } } /** Mockable wrapper of {@link BluetoothGatt}. */ @VisibleForTesting public static class BluetoothGattTestableWrapper { public final BluetoothGatt mWrappedBluetoothGatt; BluetoothGattTestableWrapper(BluetoothGatt bluetoothGatt) { mWrappedBluetoothGatt = bluetoothGatt; } /** See {@link BluetoothGatt#getServices()}. */ public List<BluetoothGattService> getServices() { return mWrappedBluetoothGatt.getServices(); } /** See {@link BluetoothGatt#getService(UUID)}. */ @Nullable public BluetoothGattService getService(UUID uuid) { return mWrappedBluetoothGatt.getService(uuid); } /** See {@link BluetoothGatt#discoverServices()}. */ public boolean discoverServices() { return mWrappedBluetoothGatt.discoverServices(); } /** * See {@link BluetoothGatt#readCharacteristic( * BluetoothGattCharacteristic)}. */ public boolean readCharacteristic(BluetoothGattCharacteristic characteristic) { return mWrappedBluetoothGatt.readCharacteristic(characteristic); } /** * See {@link BluetoothGatt#writeCharacteristic( * BluetoothGattCharacteristic, byte[], int)} . */ public boolean writeCharacteristic(BluetoothGattCharacteristic characteristic) { return mWrappedBluetoothGatt.writeCharacteristic(characteristic); } /** See {@link BluetoothGatt#readDescriptor(BluetoothGattDescriptor)}. */ public boolean readDescriptor(BluetoothGattDescriptor descriptor) { return mWrappedBluetoothGatt.readDescriptor(descriptor); } /** * See {@link BluetoothGatt#writeDescriptor(BluetoothGattDescriptor, * byte[])}. */ public boolean writeDescriptor(BluetoothGattDescriptor descriptor) { return mWrappedBluetoothGatt.writeDescriptor(descriptor); } /** See {@link BluetoothGatt#requestMtu(int)}. */ public boolean requestMtu(int mtu) { return mWrappedBluetoothGatt.requestMtu(mtu); } /** See {@link BluetoothGatt#setCharacteristicNotification}. */ public boolean setCharacteristicNotification( BluetoothGattCharacteristic characteristic, boolean enable) { return mWrappedBluetoothGatt.setCharacteristicNotification(characteristic, enable); } /** See {@link BluetoothGatt#disconnect()}. */ public void disconnect() { mWrappedBluetoothGatt.disconnect(); } /** See {@link BluetoothGatt#close()}. */ public void close() { mWrappedBluetoothGatt.close(); } } } } android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientStateMachineTest.java +147 −13 Original line number Original line Diff line number Diff line Loading @@ -21,34 +21,34 @@ import static android.bluetooth.BluetoothGatt.GATT_SUCCESS; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.after; import static org.mockito.Mockito.after; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.mockito.Mockito.when; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.reset; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothProfile; import android.content.Context; import android.content.Intent; import android.content.Intent; import android.os.Bundle; import android.os.Bundle; import android.os.HandlerThread; import android.os.HandlerThread; import android.os.Looper; import android.os.Looper; import android.os.Message; import android.telecom.Log; import androidx.test.filters.MediumTest; import androidx.test.filters.MediumTest; import com.android.bluetooth.R; import com.android.bluetooth.TestUtils; import com.android.bluetooth.TestUtils; import com.android.bluetooth.btservice.AdapterService; import com.android.bluetooth.btservice.AdapterService; import static com.google.common.truth.Truth.assertThat; import org.hamcrest.core.IsInstanceOf; import org.hamcrest.core.IsInstanceOf; import org.junit.After; import org.junit.After; import org.junit.Assert; import org.junit.Assert; import org.junit.Assume; import org.junit.Before; import org.junit.Before; import org.junit.Rule; import org.junit.Rule; import org.junit.Test; import org.junit.Test; Loading @@ -56,25 +56,29 @@ import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.junit.runners.JUnit4; import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import org.mockito.junit.MockitoRule; import java.util.UUID; @MediumTest @MediumTest @RunWith(JUnit4.class) @RunWith(JUnit4.class) public class BassClientStateMachineTest { public class BassClientStateMachineTest { @Rule @Rule public final MockitoRule mockito = MockitoJUnit.rule(); public final MockitoRule mockito = MockitoJUnit.rule(); private BluetoothAdapter mAdapter; private HandlerThread mHandlerThread; private StubBassClientStateMachine mBassClientStateMachine; private static final int CONNECTION_TIMEOUT_MS = 1_000; private static final int CONNECTION_TIMEOUT_MS = 1_000; private static final int TIMEOUT_MS = 2_000; private static final int TIMEOUT_MS = 2_000; private static final int WAIT_MS = 1_200; private static final int WAIT_MS = 1_200; private BluetoothAdapter mAdapter; private HandlerThread mHandlerThread; private StubBassClientStateMachine mBassClientStateMachine; private BluetoothDevice mTestDevice; private BluetoothDevice mTestDevice; @Mock private AdapterService mAdapterService; @Mock @Mock private BassClientService mBassClientService; private AdapterService mAdapterService; @Mock private BassClientService mBassClientService; @Before @Before public void setUp() throws Exception { public void setUp() throws Exception { Loading @@ -95,6 +99,7 @@ public class BassClientStateMachineTest { @After @After public void tearDown() throws Exception { public void tearDown() throws Exception { Log.d("hieu-debug", "blo blo"); mBassClientStateMachine.doQuit(); mBassClientStateMachine.doQuit(); mHandlerThread.quit(); mHandlerThread.quit(); TestUtils.clearAdapterService(mAdapterService); TestUtils.clearAdapterService(mAdapterService); Loading Loading @@ -221,6 +226,131 @@ public class BassClientStateMachineTest { IsInstanceOf.instanceOf(BassClientStateMachine.Disconnected.class)); IsInstanceOf.instanceOf(BassClientStateMachine.Disconnected.class)); } } @Test public void testStatesChangesWithMessages() { allowConnection(true); allowConnectGatt(true); assertThat(mBassClientStateMachine.getCurrentState()) .isInstanceOf(BassClientStateMachine.Disconnected.class); // disconnected -> connecting ---timeout---> disconnected sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(BassClientStateMachine.CONNECT), BassClientStateMachine.Connecting.class); sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(BassClientStateMachine.CONNECT_TIMEOUT), BassClientStateMachine.Disconnected.class); // disconnected -> connecting ---DISCONNECT---> disconnected sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(BassClientStateMachine.CONNECT), BassClientStateMachine.Connecting.class); sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(BassClientStateMachine.DISCONNECT), BassClientStateMachine.Disconnected.class); // disconnected -> connecting ---CONNECTION_STATE_CHANGED(connected)---> connected --> // disconnected sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(BassClientStateMachine.CONNECT), BassClientStateMachine.Connecting.class); sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage( BassClientStateMachine.CONNECTION_STATE_CHANGED, Integer.valueOf(BluetoothProfile.STATE_CONNECTED)), BassClientStateMachine.Connected.class); sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage( BassClientStateMachine.CONNECTION_STATE_CHANGED, Integer.valueOf(BluetoothProfile.STATE_DISCONNECTED)), BassClientStateMachine.Disconnected.class); // disconnected -> connecting ---CONNECTION_STATE_CHANGED(non-connected) --> disconnected sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(BassClientStateMachine.CONNECT), BassClientStateMachine.Connecting.class); sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage( BassClientStateMachine.CONNECTION_STATE_CHANGED, Integer.valueOf(BluetoothProfile.STATE_DISCONNECTED)), BassClientStateMachine.Disconnected.class); // change default state to connected for the next tests sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(BassClientStateMachine.CONNECT), BassClientStateMachine.Connecting.class); sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage( BassClientStateMachine.CONNECTION_STATE_CHANGED, Integer.valueOf(BluetoothProfile.STATE_CONNECTED)), BassClientStateMachine.Connected.class); // connected ----READ_BASS_CHARACTERISTICS---> connectedProcessing --GATT_TXN_PROCESSED // --> connected // Make bluetoothGatt non-null so state will transit mBassClientStateMachine.mBluetoothGatt = Mockito.mock( BassClientStateMachine.BluetoothGattTestableWrapper.class); mBassClientStateMachine.mBroadcastScanControlPoint = new BluetoothGattCharacteristic( BassConstants.BASS_BCAST_AUDIO_SCAN_CTRL_POINT, BluetoothGattCharacteristic.PROPERTY_READ, BluetoothGattCharacteristic.PERMISSION_READ); sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage( BassClientStateMachine.READ_BASS_CHARACTERISTICS, new BluetoothGattCharacteristic(UUID.randomUUID(), BluetoothGattCharacteristic.PROPERTY_READ, BluetoothGattCharacteristic.PERMISSION_READ)), BassClientStateMachine.ConnectedProcessing.class); sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(BassClientStateMachine.GATT_TXN_PROCESSED), BassClientStateMachine.Connected.class); // connected ----READ_BASS_CHARACTERISTICS---> connectedProcessing --GATT_TXN_TIMEOUT --> // connected sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage( BassClientStateMachine.READ_BASS_CHARACTERISTICS, new BluetoothGattCharacteristic(UUID.randomUUID(), BluetoothGattCharacteristic.PROPERTY_READ, BluetoothGattCharacteristic.PERMISSION_READ)), BassClientStateMachine.ConnectedProcessing.class); sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(BassClientStateMachine.GATT_TXN_TIMEOUT), BassClientStateMachine.Connected.class); // connected ----START_SCAN_OFFLOAD---> connectedProcessing --GATT_TXN_PROCESSED--> // connected sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(BassClientStateMachine.START_SCAN_OFFLOAD), BassClientStateMachine.ConnectedProcessing.class); sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(BassClientStateMachine.GATT_TXN_PROCESSED), BassClientStateMachine.Connected.class); // connected ----STOP_SCAN_OFFLOAD---> connectedProcessing --GATT_TXN_PROCESSED--> connected sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(BassClientStateMachine.STOP_SCAN_OFFLOAD), BassClientStateMachine.ConnectedProcessing.class); sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(BassClientStateMachine.GATT_TXN_PROCESSED), BassClientStateMachine.Connected.class); } private <T> void sendMessageAndVerifyTransition(Message msg, Class<T> type) { Mockito.clearInvocations(mBassClientService); mBassClientStateMachine.sendMessage(msg); // Verify that one connection state broadcast is executed verify(mBassClientService, timeout(TIMEOUT_MS) .times(1)) .sendBroadcast(any(Intent.class), anyString(), any()); Assert.assertThat(mBassClientStateMachine.getCurrentState(), IsInstanceOf.instanceOf(type)); } // It simulates GATT connection for testing. // It simulates GATT connection for testing. public static class StubBassClientStateMachine extends BassClientStateMachine { public static class StubBassClientStateMachine extends BassClientStateMachine { boolean mShouldAllowGatt = true; boolean mShouldAllowGatt = true; Loading @@ -238,7 +368,11 @@ public class BassClientStateMachineTest { public void notifyConnectionStateChanged(int status, int newState) { public void notifyConnectionStateChanged(int status, int newState) { if (mGattCallback != null) { if (mGattCallback != null) { mGattCallback.onConnectionStateChange(mBluetoothGatt, status, newState); BluetoothGatt gatt = null; if (mBluetoothGatt != null) { gatt = mBluetoothGatt.mWrappedBluetoothGatt; } mGattCallback.onConnectionStateChange(gatt, status, newState); } } } } } } Loading Loading
android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java +104 −6 Original line number Original line Diff line number Diff line Loading @@ -58,6 +58,7 @@ package com.android.bluetooth.bass_client; import static android.Manifest.permission.BLUETOOTH_CONNECT; import static android.Manifest.permission.BLUETOOTH_CONNECT; import android.annotation.Nullable; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGatt; Loading Loading @@ -106,6 +107,7 @@ import java.util.Iterator; import java.util.List; import java.util.List; import java.util.Map; import java.util.Map; import java.util.Scanner; import java.util.Scanner; import java.util.UUID; import java.util.stream.IntStream; import java.util.stream.IntStream; @VisibleForTesting @VisibleForTesting Loading Loading @@ -161,8 +163,8 @@ public class BassClientStateMachine extends StateMachine { private boolean mDiscoveryInitiated = false; private boolean mDiscoveryInitiated = false; @VisibleForTesting @VisibleForTesting BassClientService mService; BassClientService mService; @VisibleForTesting private BluetoothGattCharacteristic mBroadcastScanControlPoint; BluetoothGattCharacteristic mBroadcastScanControlPoint; private boolean mFirstTimeBisDiscovery = false; private boolean mFirstTimeBisDiscovery = false; private int mPASyncRetryCounter = 0; private int mPASyncRetryCounter = 0; private ScanResult mScanRes = null; private ScanResult mScanRes = null; Loading @@ -185,8 +187,8 @@ public class BassClientStateMachine extends StateMachine { private int mBroadcastSourceIdLength = 3; private int mBroadcastSourceIdLength = 3; private byte mNextSourceId = 0; private byte mNextSourceId = 0; private boolean mAllowReconnect = false; private boolean mAllowReconnect = false; @VisibleForTesting BluetoothGatt mBluetoothGatt = null; BluetoothGattTestableWrapper mBluetoothGatt = null; BluetoothGattCallback mGattCallback = null; BluetoothGattCallback mGattCallback = null; BassClientStateMachine(BluetoothDevice device, BassClientService svc, Looper looper, BassClientStateMachine(BluetoothDevice device, BassClientService svc, Looper looper, Loading Loading @@ -1011,11 +1013,16 @@ public class BassClientStateMachine extends StateMachine { mGattCallback = new GattCallback(); mGattCallback = new GattCallback(); } } mBluetoothGatt = mDevice.connectGatt(mService, autoConnect, BluetoothGatt gatt = mDevice.connectGatt(mService, autoConnect, mGattCallback, BluetoothDevice.TRANSPORT_LE, mGattCallback, BluetoothDevice.TRANSPORT_LE, (BluetoothDevice.PHY_LE_1M_MASK (BluetoothDevice.PHY_LE_1M_MASK | BluetoothDevice.PHY_LE_2M_MASK | BluetoothDevice.PHY_LE_2M_MASK | BluetoothDevice.PHY_LE_CODED_MASK), null); | BluetoothDevice.PHY_LE_CODED_MASK), null); if (gatt != null) { mBluetoothGatt = new BluetoothGattTestableWrapper(gatt); } return mBluetoothGatt != null; return mBluetoothGatt != null; } } Loading Loading @@ -1465,6 +1472,12 @@ public class BassClientStateMachine extends StateMachine { removeDeferredMessages(CONNECT); removeDeferredMessages(CONNECT); if (mLastConnectionState == BluetoothProfile.STATE_CONNECTED) { if (mLastConnectionState == BluetoothProfile.STATE_CONNECTED) { log("CONNECTED->CONNECTED: Ignore"); log("CONNECTED->CONNECTED: Ignore"); // Broadcast for testing purpose only if (Utils.isInstrumentationTestMode()) { Intent intent = new Intent("android.bluetooth.bass_client.NOTIFY_TEST"); mService.sendBroadcast(intent, BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions()); } } else { } else { broadcastConnectionState(mDevice, mLastConnectionState, broadcastConnectionState(mDevice, mLastConnectionState, BluetoothProfile.STATE_CONNECTED); BluetoothProfile.STATE_CONNECTED); Loading Loading @@ -1724,12 +1737,20 @@ public class BassClientStateMachine extends StateMachine { } } } } // public for testing, but private for non-testing @VisibleForTesting @VisibleForTesting class ConnectedProcessing extends State { class ConnectedProcessing extends State { @Override @Override public void enter() { public void enter() { log("Enter ConnectedProcessing(" + mDevice + "): " log("Enter ConnectedProcessing(" + mDevice + "): " + messageWhatToString(getCurrentMessage().what)); + messageWhatToString(getCurrentMessage().what)); // Broadcast for testing purpose only if (Utils.isInstrumentationTestMode()) { Intent intent = new Intent("android.bluetooth.bass_client.NOTIFY_TEST"); mService.sendBroadcast(intent, BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions()); } } } @Override @Override public void exit() { public void exit() { Loading Loading @@ -2023,4 +2044,81 @@ public class BassClientStateMachine extends StateMachine { } } Log.d(TAG, builder.toString()); Log.d(TAG, builder.toString()); } } /** Mockable wrapper of {@link BluetoothGatt}. */ @VisibleForTesting public static class BluetoothGattTestableWrapper { public final BluetoothGatt mWrappedBluetoothGatt; BluetoothGattTestableWrapper(BluetoothGatt bluetoothGatt) { mWrappedBluetoothGatt = bluetoothGatt; } /** See {@link BluetoothGatt#getServices()}. */ public List<BluetoothGattService> getServices() { return mWrappedBluetoothGatt.getServices(); } /** See {@link BluetoothGatt#getService(UUID)}. */ @Nullable public BluetoothGattService getService(UUID uuid) { return mWrappedBluetoothGatt.getService(uuid); } /** See {@link BluetoothGatt#discoverServices()}. */ public boolean discoverServices() { return mWrappedBluetoothGatt.discoverServices(); } /** * See {@link BluetoothGatt#readCharacteristic( * BluetoothGattCharacteristic)}. */ public boolean readCharacteristic(BluetoothGattCharacteristic characteristic) { return mWrappedBluetoothGatt.readCharacteristic(characteristic); } /** * See {@link BluetoothGatt#writeCharacteristic( * BluetoothGattCharacteristic, byte[], int)} . */ public boolean writeCharacteristic(BluetoothGattCharacteristic characteristic) { return mWrappedBluetoothGatt.writeCharacteristic(characteristic); } /** See {@link BluetoothGatt#readDescriptor(BluetoothGattDescriptor)}. */ public boolean readDescriptor(BluetoothGattDescriptor descriptor) { return mWrappedBluetoothGatt.readDescriptor(descriptor); } /** * See {@link BluetoothGatt#writeDescriptor(BluetoothGattDescriptor, * byte[])}. */ public boolean writeDescriptor(BluetoothGattDescriptor descriptor) { return mWrappedBluetoothGatt.writeDescriptor(descriptor); } /** See {@link BluetoothGatt#requestMtu(int)}. */ public boolean requestMtu(int mtu) { return mWrappedBluetoothGatt.requestMtu(mtu); } /** See {@link BluetoothGatt#setCharacteristicNotification}. */ public boolean setCharacteristicNotification( BluetoothGattCharacteristic characteristic, boolean enable) { return mWrappedBluetoothGatt.setCharacteristicNotification(characteristic, enable); } /** See {@link BluetoothGatt#disconnect()}. */ public void disconnect() { mWrappedBluetoothGatt.disconnect(); } /** See {@link BluetoothGatt#close()}. */ public void close() { mWrappedBluetoothGatt.close(); } } } }
android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientStateMachineTest.java +147 −13 Original line number Original line Diff line number Diff line Loading @@ -21,34 +21,34 @@ import static android.bluetooth.BluetoothGatt.GATT_SUCCESS; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.after; import static org.mockito.Mockito.after; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.mockito.Mockito.when; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.reset; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothProfile; import android.content.Context; import android.content.Intent; import android.content.Intent; import android.os.Bundle; import android.os.Bundle; import android.os.HandlerThread; import android.os.HandlerThread; import android.os.Looper; import android.os.Looper; import android.os.Message; import android.telecom.Log; import androidx.test.filters.MediumTest; import androidx.test.filters.MediumTest; import com.android.bluetooth.R; import com.android.bluetooth.TestUtils; import com.android.bluetooth.TestUtils; import com.android.bluetooth.btservice.AdapterService; import com.android.bluetooth.btservice.AdapterService; import static com.google.common.truth.Truth.assertThat; import org.hamcrest.core.IsInstanceOf; import org.hamcrest.core.IsInstanceOf; import org.junit.After; import org.junit.After; import org.junit.Assert; import org.junit.Assert; import org.junit.Assume; import org.junit.Before; import org.junit.Before; import org.junit.Rule; import org.junit.Rule; import org.junit.Test; import org.junit.Test; Loading @@ -56,25 +56,29 @@ import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.junit.runners.JUnit4; import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import org.mockito.junit.MockitoRule; import java.util.UUID; @MediumTest @MediumTest @RunWith(JUnit4.class) @RunWith(JUnit4.class) public class BassClientStateMachineTest { public class BassClientStateMachineTest { @Rule @Rule public final MockitoRule mockito = MockitoJUnit.rule(); public final MockitoRule mockito = MockitoJUnit.rule(); private BluetoothAdapter mAdapter; private HandlerThread mHandlerThread; private StubBassClientStateMachine mBassClientStateMachine; private static final int CONNECTION_TIMEOUT_MS = 1_000; private static final int CONNECTION_TIMEOUT_MS = 1_000; private static final int TIMEOUT_MS = 2_000; private static final int TIMEOUT_MS = 2_000; private static final int WAIT_MS = 1_200; private static final int WAIT_MS = 1_200; private BluetoothAdapter mAdapter; private HandlerThread mHandlerThread; private StubBassClientStateMachine mBassClientStateMachine; private BluetoothDevice mTestDevice; private BluetoothDevice mTestDevice; @Mock private AdapterService mAdapterService; @Mock @Mock private BassClientService mBassClientService; private AdapterService mAdapterService; @Mock private BassClientService mBassClientService; @Before @Before public void setUp() throws Exception { public void setUp() throws Exception { Loading @@ -95,6 +99,7 @@ public class BassClientStateMachineTest { @After @After public void tearDown() throws Exception { public void tearDown() throws Exception { Log.d("hieu-debug", "blo blo"); mBassClientStateMachine.doQuit(); mBassClientStateMachine.doQuit(); mHandlerThread.quit(); mHandlerThread.quit(); TestUtils.clearAdapterService(mAdapterService); TestUtils.clearAdapterService(mAdapterService); Loading Loading @@ -221,6 +226,131 @@ public class BassClientStateMachineTest { IsInstanceOf.instanceOf(BassClientStateMachine.Disconnected.class)); IsInstanceOf.instanceOf(BassClientStateMachine.Disconnected.class)); } } @Test public void testStatesChangesWithMessages() { allowConnection(true); allowConnectGatt(true); assertThat(mBassClientStateMachine.getCurrentState()) .isInstanceOf(BassClientStateMachine.Disconnected.class); // disconnected -> connecting ---timeout---> disconnected sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(BassClientStateMachine.CONNECT), BassClientStateMachine.Connecting.class); sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(BassClientStateMachine.CONNECT_TIMEOUT), BassClientStateMachine.Disconnected.class); // disconnected -> connecting ---DISCONNECT---> disconnected sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(BassClientStateMachine.CONNECT), BassClientStateMachine.Connecting.class); sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(BassClientStateMachine.DISCONNECT), BassClientStateMachine.Disconnected.class); // disconnected -> connecting ---CONNECTION_STATE_CHANGED(connected)---> connected --> // disconnected sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(BassClientStateMachine.CONNECT), BassClientStateMachine.Connecting.class); sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage( BassClientStateMachine.CONNECTION_STATE_CHANGED, Integer.valueOf(BluetoothProfile.STATE_CONNECTED)), BassClientStateMachine.Connected.class); sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage( BassClientStateMachine.CONNECTION_STATE_CHANGED, Integer.valueOf(BluetoothProfile.STATE_DISCONNECTED)), BassClientStateMachine.Disconnected.class); // disconnected -> connecting ---CONNECTION_STATE_CHANGED(non-connected) --> disconnected sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(BassClientStateMachine.CONNECT), BassClientStateMachine.Connecting.class); sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage( BassClientStateMachine.CONNECTION_STATE_CHANGED, Integer.valueOf(BluetoothProfile.STATE_DISCONNECTED)), BassClientStateMachine.Disconnected.class); // change default state to connected for the next tests sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(BassClientStateMachine.CONNECT), BassClientStateMachine.Connecting.class); sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage( BassClientStateMachine.CONNECTION_STATE_CHANGED, Integer.valueOf(BluetoothProfile.STATE_CONNECTED)), BassClientStateMachine.Connected.class); // connected ----READ_BASS_CHARACTERISTICS---> connectedProcessing --GATT_TXN_PROCESSED // --> connected // Make bluetoothGatt non-null so state will transit mBassClientStateMachine.mBluetoothGatt = Mockito.mock( BassClientStateMachine.BluetoothGattTestableWrapper.class); mBassClientStateMachine.mBroadcastScanControlPoint = new BluetoothGattCharacteristic( BassConstants.BASS_BCAST_AUDIO_SCAN_CTRL_POINT, BluetoothGattCharacteristic.PROPERTY_READ, BluetoothGattCharacteristic.PERMISSION_READ); sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage( BassClientStateMachine.READ_BASS_CHARACTERISTICS, new BluetoothGattCharacteristic(UUID.randomUUID(), BluetoothGattCharacteristic.PROPERTY_READ, BluetoothGattCharacteristic.PERMISSION_READ)), BassClientStateMachine.ConnectedProcessing.class); sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(BassClientStateMachine.GATT_TXN_PROCESSED), BassClientStateMachine.Connected.class); // connected ----READ_BASS_CHARACTERISTICS---> connectedProcessing --GATT_TXN_TIMEOUT --> // connected sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage( BassClientStateMachine.READ_BASS_CHARACTERISTICS, new BluetoothGattCharacteristic(UUID.randomUUID(), BluetoothGattCharacteristic.PROPERTY_READ, BluetoothGattCharacteristic.PERMISSION_READ)), BassClientStateMachine.ConnectedProcessing.class); sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(BassClientStateMachine.GATT_TXN_TIMEOUT), BassClientStateMachine.Connected.class); // connected ----START_SCAN_OFFLOAD---> connectedProcessing --GATT_TXN_PROCESSED--> // connected sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(BassClientStateMachine.START_SCAN_OFFLOAD), BassClientStateMachine.ConnectedProcessing.class); sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(BassClientStateMachine.GATT_TXN_PROCESSED), BassClientStateMachine.Connected.class); // connected ----STOP_SCAN_OFFLOAD---> connectedProcessing --GATT_TXN_PROCESSED--> connected sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(BassClientStateMachine.STOP_SCAN_OFFLOAD), BassClientStateMachine.ConnectedProcessing.class); sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(BassClientStateMachine.GATT_TXN_PROCESSED), BassClientStateMachine.Connected.class); } private <T> void sendMessageAndVerifyTransition(Message msg, Class<T> type) { Mockito.clearInvocations(mBassClientService); mBassClientStateMachine.sendMessage(msg); // Verify that one connection state broadcast is executed verify(mBassClientService, timeout(TIMEOUT_MS) .times(1)) .sendBroadcast(any(Intent.class), anyString(), any()); Assert.assertThat(mBassClientStateMachine.getCurrentState(), IsInstanceOf.instanceOf(type)); } // It simulates GATT connection for testing. // It simulates GATT connection for testing. public static class StubBassClientStateMachine extends BassClientStateMachine { public static class StubBassClientStateMachine extends BassClientStateMachine { boolean mShouldAllowGatt = true; boolean mShouldAllowGatt = true; Loading @@ -238,7 +368,11 @@ public class BassClientStateMachineTest { public void notifyConnectionStateChanged(int status, int newState) { public void notifyConnectionStateChanged(int status, int newState) { if (mGattCallback != null) { if (mGattCallback != null) { mGattCallback.onConnectionStateChange(mBluetoothGatt, status, newState); BluetoothGatt gatt = null; if (mBluetoothGatt != null) { gatt = mBluetoothGatt.mWrappedBluetoothGatt; } mGattCallback.onConnectionStateChange(gatt, status, newState); } } } } } } Loading