/* * Copyright 2019 Fitbit, Inc. All rights reserved. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ package com.fitbit.bluetooth.fbgatt; /* * Test gatt connection stuff */ import com.fitbit.bluetooth.fbgatt.btcopies.BluetoothGattCharacteristicCopy; import com.fitbit.bluetooth.fbgatt.btcopies.BluetoothGattDescriptorCopy; import com.fitbit.bluetooth.fbgatt.util.LooperWatchdog; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattServer; import android.content.Context; import android.os.Looper; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import java.util.ArrayList; import java.util.List; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class GattConnectionTests { private static final String MOCK_ADDRESS = "02:00:00:00:00:00"; private static final String MOCK_NAME = "foobar"; private static GattConnection connection; private static GattServerConnection serverConnection; private static BluetoothGatt mockGatt; @BeforeClass public static void beforeClass() { Context appContext = mock(Context.class); when(appContext.getSystemService(any(String.class))).thenReturn(null); when(appContext.getApplicationContext()).thenReturn(appContext); FitbitGatt.getInstance().setAsyncOperationThreadWatchdog(mock(LooperWatchdog.class)); FitbitGatt.getInstance().start(appContext); Looper mockLooper = mock(Looper.class); BluetoothDevice mockBluetoothDevice = mock(BluetoothDevice.class); when(mockBluetoothDevice.getAddress()).thenReturn(MOCK_ADDRESS); when(mockBluetoothDevice.getName()).thenReturn(MOCK_NAME); mockGatt = mock(BluetoothGatt.class); when(mockGatt.getDevice()).thenReturn(mockBluetoothDevice); connection = new GattConnection(new FitbitBluetoothDevice(mockBluetoothDevice), mockLooper); connection.setMockMode(true); BluetoothGattServer server = mock(BluetoothGattServer.class); serverConnection = new GattServerConnection(server, mockLooper); serverConnection.setMockMode(true); } @Before public void before(){ FitbitGatt.getInstance().setClientCallback(new GattClientCallback()); } @After public void after() { FitbitGatt.getInstance().setClientCallback(null); FitbitGatt.setInstance(null); } @Test public void testRemovingGattConnectionEventListeners(){ ConnectionEventListener listener = new ConnectionEventListener() { @Override public void onClientCharacteristicChanged(@NonNull TransactionResult result, @NonNull GattConnection connection) { } @Override public void onClientConnectionStateChanged(@NonNull TransactionResult result, @NonNull GattConnection connection) { } @Override public void onServicesDiscovered(@NonNull TransactionResult result, @NonNull GattConnection connection) { } @Override public void onMtuChanged(@NonNull TransactionResult result, @NonNull GattConnection connection) { } @Override public void onPhyChanged(@NonNull TransactionResult result, @NonNull GattConnection connection) { } }; connection.registerConnectionEventListener(listener); connection.unregisterConnectionEventListener(listener); Assert.assertTrue("Connection event listeners should be empty", connection.getConnectionEventListeners().isEmpty()); } @Test public void testRemovingGattServerConnectionEventListeners(){ ServerConnectionEventListener listener = new ServerConnectionEventListener() { @Override public void onServerMtuChanged(@NonNull BluetoothDevice device, @NonNull TransactionResult result, @NonNull GattServerConnection connection) { } @Override public void onServerConnectionStateChanged(@NonNull BluetoothDevice device, @NonNull TransactionResult result, @NonNull GattServerConnection connection) { } @Override public void onServerCharacteristicWriteRequest(@NonNull BluetoothDevice device, @NonNull TransactionResult result, @NonNull GattServerConnection connection) { } @Override public void onServerCharacteristicReadRequest(@NonNull BluetoothDevice device, @NonNull TransactionResult result, @NonNull GattServerConnection connection) { } @Override public void onServerDescriptorWriteRequest(@NonNull BluetoothDevice device, @NonNull TransactionResult result, @NonNull GattServerConnection connection) { } @Override public void onServerDescriptorReadRequest(@NonNull BluetoothDevice device, @NonNull TransactionResult result, @NonNull GattServerConnection connection) { } }; serverConnection.registerConnectionEventListener(listener); serverConnection.unregisterConnectionEventListener(listener); Assert.assertTrue("Server Connection event listeners should be empty", serverConnection.getConnectionEventListeners().isEmpty()); } @Test public void gattClientEventListenerShouldRegisterOne(){ GattClientListener listener = new GattClientListener() { @Nullable @Override public FitbitBluetoothDevice getDevice() { return connection.getDevice(); } @Override public void onPhyUpdate(BluetoothGatt gatt, int txPhy, int rxPhy, int status) { } @Override public void onPhyRead(BluetoothGatt gatt, int txPhy, int rxPhy, int status) { } @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { } @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { } @Override public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristicCopy characteristic, int status) { } @Override public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristicCopy characteristic, int status) { } @Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristicCopy characteristic) { } @Override public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptorCopy descriptor, int status) { } @Override public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptorCopy descriptor, int status) { } @Override public void onReliableWriteCompleted(BluetoothGatt gatt, int status) { } @Override public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) { } @Override public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) { } }; connection.registerGattClientListener(listener); List<GattClientListener> listnerList = FitbitGatt.getInstance().getClientCallback().getGattClientListeners(); Assert.assertEquals(1, listnerList.size()); connection.unregisterGattClientListener(listener); listnerList = FitbitGatt.getInstance().getClientCallback().getGattClientListeners(); Assert.assertEquals(0, listnerList.size()); } @Test public void gattClientEventListenerShouldReceiveCallback(){ GattClientListener listener = new GattClientListener() { @Nullable @Override public FitbitBluetoothDevice getDevice() { return connection.getDevice(); } @Override public void onPhyUpdate(BluetoothGatt gatt, int txPhy, int rxPhy, int status) { Assert.assertEquals(1, status); } @Override public void onPhyRead(BluetoothGatt gatt, int txPhy, int rxPhy, int status) { } @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { } @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { } @Override public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristicCopy characteristic, int status) { } @Override public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristicCopy characteristic, int status) { } @Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristicCopy characteristic) { } @Override public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptorCopy descriptor, int status) { } @Override public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptorCopy descriptor, int status) { } @Override public void onReliableWriteCompleted(BluetoothGatt gatt, int status) { } @Override public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) { } @Override public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) { } }; connection.registerGattClientListener(listener); List<GattClientListener> listenerList = FitbitGatt.getInstance().getClientCallback().getGattClientListeners(); Assert.assertEquals(1, listenerList.size()); FitbitGatt.getInstance().getClientCallback().onPhyUpdate(mockGatt, 1, 1, 1); connection.unregisterGattClientListener(listener); listenerList = FitbitGatt.getInstance().getClientCallback().getGattClientListeners(); Assert.assertEquals(0, listenerList.size()); } @Test public void testNullEventListenerCrashes() { try { connection.registerConnectionEventListener(null); fail("A NullPointerException was expected"); } catch (NullPointerException npe) { //This is expected. } try { connection.unregisterConnectionEventListener(null); fail("A NullPointerException was expected"); } catch (NullPointerException npe) { //This is expected. } try { serverConnection.registerConnectionEventListener(null); fail("A NullPointerException was expected"); } catch (NullPointerException npe) { //This is expected. } try { serverConnection.unregisterConnectionEventListener(null); fail("A NullPointerException was expected"); } catch (NullPointerException npe) { //This is expected. } } @Test public void testHighlyConcurrentAccess() { List<Thread> threadList = new ArrayList<>(); for (int i = 0; i < 5; i++) { threadList.add(createRegisteringThread()); } for (Thread thread : threadList) { thread.start(); } for (Thread thread : threadList) { try { thread.join(); } catch (InterruptedException e) { e.printStackTrace(); fail(); } } assertEquals(5000, connection.getConnectionEventListeners().size()); } private Thread createRegisteringThread() { return new Thread() { @Override public void run() { for (int i = 0; i < 1000; i++) { connection.registerConnectionEventListener(new ConnectionEventListener() { @Override public void onClientCharacteristicChanged(@NonNull TransactionResult result, @NonNull GattConnection connection) { } @Override public void onClientConnectionStateChanged(@NonNull TransactionResult result, @NonNull GattConnection connection) { } @Override public void onServicesDiscovered(@NonNull TransactionResult result, @NonNull GattConnection connection) { } @Override public void onMtuChanged(@NonNull TransactionResult result, @NonNull GattConnection connection) { } @Override public void onPhyChanged(@NonNull TransactionResult result, @NonNull GattConnection connection) { } }); } } }; } }