blob: acec1f563d065e9339796ae297262fc43fe8c006 [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.car.connecteddevice.ble;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mockitoSession;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.le.AdvertiseCallback;
import android.bluetooth.le.AdvertiseData;
import android.bluetooth.le.AdvertiseSettings;
import android.os.ParcelUuid;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.car.connecteddevice.AssociationCallback;
import com.android.car.connecteddevice.model.AssociatedDevice;
import com.android.car.connecteddevice.storage.ConnectedDeviceStorage;
import com.android.car.connecteddevice.util.ByteUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
import java.time.Duration;
import java.util.UUID;
@RunWith(AndroidJUnit4.class)
public class CarBlePeripheralManagerTest {
private static final UUID ASSOCIATION_SERVICE_UUID = UUID.randomUUID();
private static final UUID RECONNECT_SERVICE_UUID = UUID.randomUUID();
private static final UUID RECONNECT_DATA_UUID = UUID.randomUUID();
private static final UUID WRITE_UUID = UUID.randomUUID();
private static final UUID READ_UUID = UUID.randomUUID();
private static final int DEVICE_NAME_LENGTH_LIMIT = 8;
private static final String TEST_REMOTE_DEVICE_ADDRESS = "00:11:22:33:AA:BB";
private static final UUID TEST_REMOTE_DEVICE_ID = UUID.randomUUID();
private static final String TEST_VERIFICATION_CODE = "000000";
private static final Duration RECONNECT_ADVERTISEMENT_DURATION = Duration.ofSeconds(2);
private static final int DEFAULT_MTU_SIZE = 23;
@Mock
private BlePeripheralManager mMockPeripheralManager;
@Mock
private ConnectedDeviceStorage mMockStorage;
@Mock
private AssociationCallback mAssociationCallback;
private CarBlePeripheralManager mCarBlePeripheralManager;
private MockitoSession mMockitoSession;
@Before
public void setUp() {
mMockitoSession = mockitoSession()
.initMocks(this)
.strictness(Strictness.WARN)
.startMocking();
mCarBlePeripheralManager = new CarBlePeripheralManager(mMockPeripheralManager, mMockStorage,
ASSOCIATION_SERVICE_UUID, RECONNECT_SERVICE_UUID, RECONNECT_DATA_UUID,
WRITE_UUID, READ_UUID, RECONNECT_ADVERTISEMENT_DURATION, DEFAULT_MTU_SIZE);
mCarBlePeripheralManager.start();
}
@After
public void tearDown() {
if (mCarBlePeripheralManager != null) {
mCarBlePeripheralManager.stop();
}
if (mMockitoSession != null) {
mMockitoSession.finishMocking();
}
}
@Test
public void testStartAssociationAdvertisingSuccess() {
String testDeviceName = getNameForAssociation();
startAssociation(mAssociationCallback, testDeviceName);
ArgumentCaptor<AdvertiseData> advertiseDataCaptor =
ArgumentCaptor.forClass(AdvertiseData.class);
ArgumentCaptor<AdvertiseData> scanResponseDataCaptor =
ArgumentCaptor.forClass(AdvertiseData.class);
verify(mMockPeripheralManager).startAdvertising(any(), advertiseDataCaptor.capture(),
scanResponseDataCaptor.capture(), any());
AdvertiseData advertisementData = advertiseDataCaptor.getValue();
ParcelUuid serviceUuid = new ParcelUuid(ASSOCIATION_SERVICE_UUID);
assertThat(advertisementData.getServiceUuids()).contains(serviceUuid);
AdvertiseData scanResponseData = scanResponseDataCaptor.getValue();
assertThat(scanResponseData.getIncludeDeviceName()).isFalse();
ParcelUuid dataUuid = new ParcelUuid(RECONNECT_DATA_UUID);
assertThat(scanResponseData.getServiceData().get(dataUuid)).isEqualTo(
testDeviceName.getBytes());
}
@Test
public void testStartAssociationAdvertisingFailure() {
startAssociation(mAssociationCallback, getNameForAssociation());
ArgumentCaptor<AdvertiseCallback> callbackCaptor =
ArgumentCaptor.forClass(AdvertiseCallback.class);
verify(mMockPeripheralManager).startAdvertising(any(), any(), any(),
callbackCaptor.capture());
AdvertiseCallback advertiseCallback = callbackCaptor.getValue();
int testErrorCode = 2;
advertiseCallback.onStartFailure(testErrorCode);
verify(mAssociationCallback).onAssociationStartFailure();
}
@Test
public void testNotifyAssociationSuccess() {
String testDeviceName = getNameForAssociation();
startAssociation(mAssociationCallback, testDeviceName);
ArgumentCaptor<AdvertiseCallback> callbackCaptor =
ArgumentCaptor.forClass(AdvertiseCallback.class);
verify(mMockPeripheralManager).startAdvertising(any(), any(), any(),
callbackCaptor.capture());
AdvertiseCallback advertiseCallback = callbackCaptor.getValue();
AdvertiseSettings settings = new AdvertiseSettings.Builder().build();
advertiseCallback.onStartSuccess(settings);
verify(mAssociationCallback).onAssociationStartSuccess(eq(testDeviceName));
}
@Test
public void testShowVerificationCode() {
AssociationSecureChannel channel = getChannelForAssociation(mAssociationCallback);
channel.getShowVerificationCodeListener().showVerificationCode(TEST_VERIFICATION_CODE);
verify(mAssociationCallback).onVerificationCodeAvailable(eq(TEST_VERIFICATION_CODE));
}
@Test
public void testAssociationSuccess() {
SecureBleChannel channel = getChannelForAssociation(mAssociationCallback);
SecureBleChannel.Callback channelCallback = channel.getCallback();
assertThat(channelCallback).isNotNull();
channelCallback.onDeviceIdReceived(TEST_REMOTE_DEVICE_ID.toString());
channelCallback.onSecureChannelEstablished();
ArgumentCaptor<AssociatedDevice> deviceCaptor =
ArgumentCaptor.forClass(AssociatedDevice.class);
verify(mMockStorage).addAssociatedDeviceForActiveUser(deviceCaptor.capture());
AssociatedDevice device = deviceCaptor.getValue();
assertThat(device.getDeviceId()).isEqualTo(TEST_REMOTE_DEVICE_ID.toString());
verify(mAssociationCallback).onAssociationCompleted(eq(TEST_REMOTE_DEVICE_ID.toString()));
}
@Test
public void testAssociationFailure_channelError() {
SecureBleChannel channel = getChannelForAssociation(mAssociationCallback);
SecureBleChannel.Callback channelCallback = channel.getCallback();
int testErrorCode = 1;
assertThat(channelCallback).isNotNull();
channelCallback.onDeviceIdReceived(TEST_REMOTE_DEVICE_ID.toString());
channelCallback.onEstablishSecureChannelFailure(testErrorCode);
verify(mAssociationCallback).onAssociationError(eq(testErrorCode));
}
@Test
public void connectToDevice_stopsAdvertisingAfterTimeout() {
when(mMockStorage.hashWithChallengeSecret(any(), any()))
.thenReturn(ByteUtils.randomBytes(32));
mCarBlePeripheralManager.connectToDevice(UUID.randomUUID());
ArgumentCaptor<AdvertiseCallback> callbackCaptor =
ArgumentCaptor.forClass(AdvertiseCallback.class);
verify(mMockPeripheralManager).startAdvertising(any(), any(), any(),
callbackCaptor.capture());
callbackCaptor.getValue().onStartSuccess(null);
verify(mMockPeripheralManager,
timeout(RECONNECT_ADVERTISEMENT_DURATION.plusSeconds(1).toMillis()))
.stopAdvertising(any(AdvertiseCallback.class));
}
@Test
public void disconnectDevice_stopsAdvertisingForPendingReconnect() {
when(mMockStorage.hashWithChallengeSecret(any(), any()))
.thenReturn(ByteUtils.randomBytes(32));
UUID deviceId = UUID.randomUUID();
mCarBlePeripheralManager.connectToDevice(deviceId);
reset(mMockPeripheralManager);
mCarBlePeripheralManager.disconnectDevice(deviceId.toString());
verify(mMockPeripheralManager).cleanup();
}
private BlePeripheralManager.Callback startAssociation(AssociationCallback callback,
String deviceName) {
ArgumentCaptor<BlePeripheralManager.Callback> callbackCaptor =
ArgumentCaptor.forClass(BlePeripheralManager.Callback.class);
mCarBlePeripheralManager.startAssociation(deviceName, callback);
verify(mMockPeripheralManager, timeout(3000)).registerCallback(callbackCaptor.capture());
return callbackCaptor.getValue();
}
private AssociationSecureChannel getChannelForAssociation(AssociationCallback callback) {
BlePeripheralManager.Callback bleManagerCallback = startAssociation(callback,
getNameForAssociation());
BluetoothDevice bluetoothDevice = BluetoothAdapter.getDefaultAdapter()
.getRemoteDevice(TEST_REMOTE_DEVICE_ADDRESS);
bleManagerCallback.onRemoteDeviceConnected(bluetoothDevice);
return (AssociationSecureChannel) mCarBlePeripheralManager.getConnectedDeviceChannel();
}
private String getNameForAssociation() {
return ByteUtils.generateRandomNumberString(DEVICE_NAME_LENGTH_LIMIT);
}
}