blob: 5ecf6fd2e06324a2976b7a1285eac59830d63b6b [file] [log] [blame]
package com.android.clockwork.bluetooth.proxy;
import static org.hamcrest.CoreMatchers.containsString;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattServer;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.DeadSystemRuntimeException;
import com.android.internal.util.IndentingPrintWriter;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
/**
* Test for {@link ProxyGattServer}
*/
@RunWith(RobolectricTestRunner.class)
public class ProxyGattServerTest {
private static final int REQUEST_ID = 7;
private static final int ZERO_OFFSET = 0;
@Mock
Context mMockContext;
@Mock
BluetoothDevice mMockDevice;
@Mock
BluetoothGattServer mMockGattServer;
@Mock
BluetoothManager mMockBluetoothManager;
@Mock
PackageManager mMockPackageManager;
@Mock
PackageInfo mMockPackageInfo;
@Mock
IndentingPrintWriter mMockPrinter;
@Mock
ProxyGattServer.Listener mMockListener;
private BluetoothGattCharacteristic mConfigCharacteristic;
private BluetoothGattCharacteristic mPingCharacteristic;
private BluetoothGattCharacteristic mUnknownCharacteristic;
private BluetoothGattDescriptor mPingCccdDescriptor;
private BluetoothGattDescriptor mUnknownDescriptor;
private BluetoothGattDescriptor mUnknownCccdDescriptor;
private ProxyGattServer mServer;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mMockContext.getSystemService(BluetoothManager.class)).thenReturn(
mMockBluetoothManager);
when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
BluetoothGattService service = ProxyGattService.createGattService();
mConfigCharacteristic = service.getCharacteristic(ProxyGattService.CONFIG_UUID);
Assert.assertNotNull(mConfigCharacteristic);
mPingCharacteristic = service.getCharacteristic(ProxyGattService.PING_UUID);
Assert.assertNotNull(mPingCharacteristic);
mPingCccdDescriptor =
mPingCharacteristic.getDescriptor(
ProxyGattService.CLIENT_CHARACTERISTIC_CONFIGURATION_DESCRIPTOR_UUID);
Assert.assertNotNull(mPingCccdDescriptor);
mUnknownCharacteristic = new BluetoothGattCharacteristic(UUID.randomUUID(), 0, 0);
mUnknownDescriptor = new BluetoothGattDescriptor(UUID.randomUUID(), 0);
mUnknownCccdDescriptor =
new BluetoothGattDescriptor(
ProxyGattService.CLIENT_CHARACTERISTIC_CONFIGURATION_DESCRIPTOR_UUID, 0);
mUnknownCharacteristic.addDescriptor(mUnknownDescriptor);
mUnknownCharacteristic.addDescriptor(mUnknownCccdDescriptor);
mServer = new ProxyGattServer(mMockContext);
mServer.setListener(mMockListener);
mServer.setCompanionDevice(mMockDevice);
when(mMockBluetoothManager.openGattServer(mMockContext, mServer.mGattServerCallback))
.thenReturn(mMockGattServer);
when(mMockGattServer.addService(any())).thenReturn(true);
when(mMockGattServer.getService(ProxyGattService.SERVICE_UUID)).thenReturn(service);
when(mMockGattServer.notifyCharacteristicChanged(any(), any(), anyBoolean())).thenReturn(
true);
mServer.start();
}
@Test
public void testConfigWriteRequestUpdatesListenerAndReceivesSuccessfulResponse() {
byte[] configData =
ProxyConfig.newBuilder()
.setPsmValue(192)
.setChannelChangeId(1234)
.setMinPingIntervalSeconds(10)
.build()
.toByteArray();
mServer.mGattServerCallback.onCharacteristicWriteRequest(
mMockDevice, REQUEST_ID, mConfigCharacteristic, false, true, ZERO_OFFSET,
configData);
verify(mMockGattServer)
.sendResponse(mMockDevice, REQUEST_ID, BluetoothGatt.GATT_SUCCESS, ZERO_OFFSET,
null);
verify(mMockListener).onProxyConfigUpdate(192, 1234, 10);
}
@Test
public void testConfigWriteRequestReceivesFailureResponseIfCompanionIsNotSet() {
mServer.setCompanionDevice(null);
mServer.mGattServerCallback.onCharacteristicWriteRequest(
mMockDevice, REQUEST_ID, mConfigCharacteristic, false, true, ZERO_OFFSET,
getConfigData());
verify(mMockGattServer)
.sendResponse(mMockDevice, REQUEST_ID, BluetoothGatt.GATT_FAILURE, ZERO_OFFSET,
null);
verifyNoInteractions(mMockListener);
}
@Test
public void testConfigWriteRequestReceivesFailureResponseIfUuidIsUnknown() {
mServer.mGattServerCallback.onCharacteristicWriteRequest(
mMockDevice, REQUEST_ID, mUnknownCharacteristic, false, true, ZERO_OFFSET,
getConfigData());
verify(mMockGattServer)
.sendResponse(mMockDevice, REQUEST_ID, BluetoothGatt.GATT_FAILURE, ZERO_OFFSET,
null);
verifyNoInteractions(mMockListener);
}
@Test
public void testConfigWriteRequestWithInvalidDataReceivesFailureResponse() {
byte[] configData = "not a proto".getBytes(StandardCharsets.UTF_8);
mServer.mGattServerCallback.onCharacteristicWriteRequest(
mMockDevice, REQUEST_ID, mConfigCharacteristic, false, true, ZERO_OFFSET,
configData);
verify(mMockGattServer)
.sendResponse(mMockDevice, REQUEST_ID, BluetoothGatt.GATT_FAILURE, ZERO_OFFSET,
null);
verifyNoInteractions(mMockListener);
}
@Test
public void testConfigWriteRequestWithNullDataReceivesFailureResponse() {
mServer.mGattServerCallback.onCharacteristicWriteRequest(
mMockDevice, REQUEST_ID, mConfigCharacteristic, false, true, ZERO_OFFSET, null);
verify(mMockGattServer)
.sendResponse(mMockDevice, REQUEST_ID, BluetoothGatt.GATT_FAILURE, ZERO_OFFSET,
null);
verifyNoInteractions(mMockListener);
}
@Test
public void testConfigWriteRequestDoesNotReceiveResponseIfNotNeeded() {
mServer.mGattServerCallback.onCharacteristicWriteRequest(
mMockDevice, REQUEST_ID, mConfigCharacteristic, false, false, ZERO_OFFSET,
getConfigData());
verify(mMockGattServer, never()).sendResponse(any(), anyInt(), anyInt(), anyInt(), any());
}
@Test
public void testDescriptorWriteRequestUpdatesNotificationStateAndReceivesSuccessfulResponse() {
mServer.mGattServerCallback.onDescriptorWriteRequest(
mMockDevice,
REQUEST_ID,
mPingCccdDescriptor,
false,
true,
ZERO_OFFSET,
BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
Assert.assertTrue(mServer.mPingNotificationsEnabled);
mServer.mGattServerCallback.onDescriptorWriteRequest(
mMockDevice,
REQUEST_ID,
mPingCccdDescriptor,
false,
true,
ZERO_OFFSET,
BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
Assert.assertFalse(mServer.mPingNotificationsEnabled);
verify(mMockGattServer, times(2))
.sendResponse(mMockDevice, REQUEST_ID, BluetoothGatt.GATT_SUCCESS, ZERO_OFFSET,
null);
}
@Test
public void testDescriptorWriteRequestReceivesFailureIfCompanionIsNotSet() {
mServer.setCompanionDevice(null);
mServer.mGattServerCallback.onDescriptorWriteRequest(
mMockDevice,
REQUEST_ID,
mPingCccdDescriptor,
false,
true,
ZERO_OFFSET,
BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
verify(mMockGattServer)
.sendResponse(mMockDevice, REQUEST_ID, BluetoothGatt.GATT_FAILURE, ZERO_OFFSET,
null);
Assert.assertFalse(mServer.mPingNotificationsEnabled);
}
@Test
public void testDescriptorWriteRequestWithUnknownDescriptorUuidReceivesFailure() {
mServer.mGattServerCallback.onDescriptorWriteRequest(
mMockDevice,
REQUEST_ID,
mUnknownDescriptor,
false,
true,
ZERO_OFFSET,
BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
verify(mMockGattServer)
.sendResponse(mMockDevice, REQUEST_ID, BluetoothGatt.GATT_FAILURE, ZERO_OFFSET,
null);
Assert.assertFalse(mServer.mPingNotificationsEnabled);
}
@Test
public void testDescriptorWriteRequestWithUnknownCharacteristicUuidReceivesFailure() {
mServer.mGattServerCallback.onDescriptorWriteRequest(
mMockDevice,
REQUEST_ID,
mUnknownCccdDescriptor,
false,
true,
ZERO_OFFSET,
BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
verify(mMockGattServer)
.sendResponse(mMockDevice, REQUEST_ID, BluetoothGatt.GATT_FAILURE, ZERO_OFFSET,
null);
Assert.assertFalse(mServer.mPingNotificationsEnabled);
}
@Test
public void testDescriptorWriteRequestWithNullDataReceivesFailure() {
mServer.mGattServerCallback.onDescriptorWriteRequest(
mMockDevice, REQUEST_ID, mPingCccdDescriptor, false, true, ZERO_OFFSET, null);
verify(mMockGattServer)
.sendResponse(mMockDevice, REQUEST_ID, BluetoothGatt.GATT_FAILURE, ZERO_OFFSET,
null);
Assert.assertFalse(mServer.mPingNotificationsEnabled);
}
@Test
public void testDescriptorWriteRequestWithInvalidDataReceivesFailure() {
byte[] value = "invalid value".getBytes(StandardCharsets.UTF_8);
mServer.mGattServerCallback.onDescriptorWriteRequest(
mMockDevice, REQUEST_ID, mPingCccdDescriptor, false, true, ZERO_OFFSET, value);
verify(mMockGattServer)
.sendResponse(mMockDevice, REQUEST_ID, BluetoothGatt.GATT_FAILURE, ZERO_OFFSET,
null);
Assert.assertFalse(mServer.mPingNotificationsEnabled);
}
@Test
public void testDescriptorWriteRequestDoesNotSendResponseIfNotNeeded() {
mServer.mGattServerCallback.onDescriptorWriteRequest(
mMockDevice,
REQUEST_ID,
mPingCccdDescriptor,
false,
false,
ZERO_OFFSET,
BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
Assert.assertTrue(mServer.mPingNotificationsEnabled);
verify(mMockGattServer, never()).sendResponse(any(), anyInt(), anyInt(), anyInt(), any());
}
@Test
public void testDescriptorReadRequestReceivesEnabledResponseWhenNotificationsEnabled() {
mServer.mPingNotificationsEnabled = true;
mServer.mGattServerCallback.onDescriptorReadRequest(
mMockDevice, REQUEST_ID, ZERO_OFFSET, mPingCccdDescriptor);
verify(mMockGattServer)
.sendResponse(
mMockDevice,
REQUEST_ID,
BluetoothGatt.GATT_SUCCESS,
ZERO_OFFSET,
BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
}
@Test
public void testDescriptorReadRequestReceivesDisabledResponseWhenNotificationsDisabled() {
mServer.mPingNotificationsEnabled = false;
mServer.mGattServerCallback.onDescriptorReadRequest(
mMockDevice, REQUEST_ID, ZERO_OFFSET, mPingCccdDescriptor);
verify(mMockGattServer)
.sendResponse(
mMockDevice,
REQUEST_ID,
BluetoothGatt.GATT_SUCCESS,
ZERO_OFFSET,
BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
}
@Test
public void testDescriptorReadRequestReceivesFailureResponseIfCompanionIsNotSet() {
mServer.setCompanionDevice(null);
mServer.mGattServerCallback.onDescriptorReadRequest(
mMockDevice, REQUEST_ID, ZERO_OFFSET, mPingCccdDescriptor);
verify(mMockGattServer)
.sendResponse(mMockDevice, REQUEST_ID, BluetoothGatt.GATT_FAILURE, ZERO_OFFSET,
null);
}
@Test
public void testDescriptorReadRequestWithUnknownDescriptorUuidReceivesFailureResponse() {
mServer.setCompanionDevice(null);
mServer.mGattServerCallback.onDescriptorReadRequest(
mMockDevice, REQUEST_ID, ZERO_OFFSET, mUnknownDescriptor);
verify(mMockGattServer)
.sendResponse(mMockDevice, REQUEST_ID, BluetoothGatt.GATT_FAILURE, ZERO_OFFSET,
null);
}
@Test
public void testDescriptorReadRequestWithUnknownCharacteristicUuidReceivesFailureResponse() {
mServer.setCompanionDevice(null);
mServer.mGattServerCallback.onDescriptorReadRequest(
mMockDevice, REQUEST_ID, ZERO_OFFSET, mUnknownCccdDescriptor);
verify(mMockGattServer)
.sendResponse(mMockDevice, REQUEST_ID, BluetoothGatt.GATT_FAILURE, ZERO_OFFSET,
null);
}
@Test
public void testSendPingNotifiesPingCharacteristic() {
mServer.mPingNotificationsEnabled = true;
Assert.assertTrue(mServer.sendPing());
verify(mMockGattServer).notifyCharacteristicChanged(mMockDevice, mPingCharacteristic,
false);
Assert.assertArrayEquals(mPingCharacteristic.getValue(), new byte[0]);
}
@Test
public void testSendPingFailsIfServerStopped() {
mServer.mPingNotificationsEnabled = true;
mServer.stop();
Assert.assertFalse(mServer.sendPing());
verify(mMockGattServer, never()).notifyCharacteristicChanged(any(), any(), anyBoolean());
}
@Test
public void testSendPingFailsIfBluetoothProcessDied() {
mServer.mPingNotificationsEnabled = true;
when(mMockGattServer.notifyCharacteristicChanged(mMockDevice, mPingCharacteristic, false))
.thenThrow(DeadSystemRuntimeException.class);
Assert.assertFalse(mServer.sendPing());
}
@Test
public void testSendPingFailsIfCompanionIsNotSet() {
mServer.mPingNotificationsEnabled = true;
mServer.setCompanionDevice(null);
Assert.assertFalse(mServer.sendPing());
verify(mMockGattServer, never()).notifyCharacteristicChanged(any(), any(), anyBoolean());
}
@Test
public void testSendPingFailsIfServiceIsNotFound() {
mServer.mPingNotificationsEnabled = true;
when(mMockGattServer.getService(ProxyGattService.SERVICE_UUID)).thenReturn(null);
Assert.assertFalse(mServer.sendPing());
verify(mMockGattServer, never()).notifyCharacteristicChanged(any(), any(), anyBoolean());
}
@Test
public void testSendPingFailsIfPingCharacteristicIsNotFound() {
mServer.mPingNotificationsEnabled = true;
// Can't remove the characteristics from the existing service so a new service without the
// ping characteristic is used instead.
BluetoothGattService emptyService =
new BluetoothGattService(
ProxyGattService.SERVICE_UUID, BluetoothGattService.SERVICE_TYPE_PRIMARY);
when(mMockGattServer.getService(ProxyGattService.SERVICE_UUID)).thenReturn(emptyService);
Assert.assertFalse(mServer.sendPing());
verify(mMockGattServer, never()).notifyCharacteristicChanged(any(), any(), anyBoolean());
}
@Test
public void testSendPingFailsIfNotificationsAreDisabled() {
mServer.mPingNotificationsEnabled = false;
Assert.assertFalse(mServer.sendPing());
verify(mMockGattServer, never()).notifyCharacteristicChanged(any(), any(), anyBoolean());
}
@Test
public void testSendPingFailsIfNotifyingFails() {
mServer.mPingNotificationsEnabled = true;
when(mMockGattServer.notifyCharacteristicChanged(any(), any(), anyBoolean())).thenReturn(
false);
Assert.assertFalse(mServer.sendPing());
}
@Test
public void testStopClosesGattServer() {
mServer.stop();
verify(mMockGattServer).close();
}
@Test
public void testDumpWhenServerStarted() {
// Server already starts in setup.
mServer.mPingNotificationsEnabled = true;
String dumpString = getDumpString(mServer);
Assert.assertThat(dumpString, containsString("Server status=Started"));
Assert.assertThat(dumpString, containsString("Ping notifications enabled=true"));
Assert.assertThat(dumpString, containsString("Companion set=true"));
}
@Test
public void testDumpWhenServerStopped() {
mServer.stop();
mServer.setCompanionDevice(null);
String dumpString = getDumpString(mServer);
Assert.assertThat(dumpString, containsString("Server status=Stopped"));
Assert.assertThat(dumpString, containsString("Ping notifications enabled=false"));
Assert.assertThat(dumpString, containsString("Companion set=false"));
}
private byte[] getConfigData() {
return ProxyConfig.newBuilder()
.setPsmValue(192)
.setMinPingIntervalSeconds(10)
.build()
.toByteArray();
}
private static String getDumpString(ProxyGattServer mServer) {
StringWriter stringWriter = new StringWriter();
IndentingPrintWriter printWriter = new IndentingPrintWriter(stringWriter, " ");
mServer.dump(printWriter);
return stringWriter.toString();
}
}