blob: e2d5c8984f4648c758bfad6cbf418d78237bb0f8 [file] [log] [blame]
/*
* Copyright (C) 2020 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.google.android.connecteddevice.ui;
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.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.Application;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothManager;
import android.os.ParcelUuid;
import android.os.RemoteException;
import androidx.arch.core.executor.testing.InstantTaskExecutorRule;
import androidx.lifecycle.Observer;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.connecteddevice.api.FakeConnector;
import com.google.android.connecteddevice.api.IAssociationCallback;
import com.google.android.connecteddevice.model.AssociatedDevice;
import com.google.android.connecteddevice.model.ConnectedDevice;
import com.google.android.connecteddevice.model.OobData;
import com.google.android.connecteddevice.model.StartAssociationResponse;
import com.google.android.connecteddevice.ui.AssociatedDeviceDetails.ConnectionState;
import com.google.android.connecteddevice.ui.AssociatedDeviceViewModel.AssociationState;
import com.google.common.collect.ImmutableList;
import java.util.List;
import java.util.UUID;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@RunWith(AndroidJUnit4.class)
public final class AssociatedDeviceViewModelTest {
private static final String TEST_ASSOCIATED_DEVICE_ID = "test_device_id";
private static final String TEST_ASSOCIATED_DEVICE_NAME_1 = "test_device_name_1";
private static final String TEST_ASSOCIATED_DEVICE_NAME_2 = "test_device_name_2";
private static final String TEST_ASSOCIATED_DEVICE_ADDRESS = "test_device_address";
private static final String TEST_CAR_NAME = "test_car_name";
private static final String TEST_VERIFICATION_CODE = "test_code";
private static final String TEST_BLE_DEVICE_NAME_PREFIX = "TestPrefix";
private static final OobData TEST_OOB_DATA = new OobData(new byte[0], new byte[0], new byte[0]);
@Rule public final MockitoRule mockito = MockitoJUnit.rule();
@Rule
public final InstantTaskExecutorRule instantTaskExecutorRule = new InstantTaskExecutorRule();
private final Application application = ApplicationProvider.getApplicationContext();
private final BluetoothAdapter adapter =
application.getSystemService(BluetoothManager.class).getAdapter();
private final FakeConnector fakeConnector = spy(new FakeConnector());
@Mock private Observer<AssociationState> mockAssociationStateObserver;
@Mock private Observer<List<AssociatedDeviceDetails>> mockDeviceDetailsObserver;
@Mock private Observer<String> mockCarNameObserver;
@Mock private Observer<String> mockPairingCodeObserver;
@Mock private Observer<AssociatedDevice> mockRemovedDeviceObserver;
private AssociatedDeviceViewModel viewModel;
private IAssociationCallback associationCallback;
@Before
public void setUp() throws RemoteException {
viewModel =
new AssociatedDeviceViewModel(
application,
/* isSppEnabled= */ false,
TEST_BLE_DEVICE_NAME_PREFIX,
/* isPassengerEnabled= */ false,
fakeConnector);
adapter.enable();
}
@Test
public void acceptVerification() throws RemoteException {
viewModel.acceptVerification();
verify(fakeConnector).acceptVerification();
}
@Test
public void removeDevice() {
AssociatedDevice device = createAssociatedDevice(/* isConnectionEnabled= */ true);
fakeConnector.addAssociatedDevice(device);
viewModel.removeDevice(device);
verify(fakeConnector).removeAssociatedDevice(eq(TEST_ASSOCIATED_DEVICE_ID));
}
@Test
public void toggleConnectionStatusForDevice_disableDevice() {
AssociatedDevice device = createAssociatedDevice(/* isConnectionEnabled= */ true);
fakeConnector.addAssociatedDevice(device);
viewModel.toggleConnectionStatusForDevice(device);
verify(fakeConnector).disableAssociatedDeviceConnection(eq(TEST_ASSOCIATED_DEVICE_ID));
}
@Test
public void toggleConnectionStatusForDevice_enableDevice() {
AssociatedDevice device = createAssociatedDevice(/* isConnectionEnabled= */ false);
fakeConnector.addAssociatedDevice(device);
viewModel.toggleConnectionStatusForDevice(device);
verify(fakeConnector).enableAssociatedDeviceConnection(eq(TEST_ASSOCIATED_DEVICE_ID));
}
@Test
public void startAssociation_startWithIdentifier() {
ParcelUuid testIdentifier = new ParcelUuid(UUID.randomUUID());
viewModel.startAssociation(testIdentifier);
verify(fakeConnector).startAssociation(eq(testIdentifier), any());
}
@Test
public void startAssociation_deviceNotReady() {
adapter.disable();
viewModel.startAssociation();
viewModel.getAssociationState().observeForever(mockAssociationStateObserver);
verify(mockAssociationStateObserver).onChanged(AssociationState.PENDING);
verify(fakeConnector, never()).startAssociation(any());
}
@Test
public void startAssociation_waitingForPasswordSetup() {
viewModel.startAssociation();
viewModel.getAssociationState().observeForever(mockAssociationStateObserver);
verify(mockAssociationStateObserver).onChanged(AssociationState.STARTING);
verify(fakeConnector).startAssociation(any());
}
@Test
public void startAssociation_inProgress() throws RemoteException {
StartAssociationResponse response =
new StartAssociationResponse(TEST_OOB_DATA, new byte[0], TEST_CAR_NAME);
viewModel.startAssociation();
captureAssociationCallback();
associationCallback.onAssociationStartSuccess(response);
viewModel.getAssociationState().observeForever(mockAssociationStateObserver);
viewModel.getAdvertisedCarName().observeForever(mockCarNameObserver);
verify(mockAssociationStateObserver).onChanged(AssociationState.STARTED);
verify(mockCarNameObserver).onChanged(TEST_BLE_DEVICE_NAME_PREFIX + TEST_CAR_NAME);
}
@Test
public void startAssociation_waitingForVerification() throws RemoteException {
StartAssociationResponse response =
new StartAssociationResponse(TEST_OOB_DATA, new byte[0], TEST_CAR_NAME);
viewModel.startAssociation();
captureAssociationCallback();
associationCallback.onAssociationStartSuccess(response);
associationCallback.onVerificationCodeAvailable(TEST_VERIFICATION_CODE);
viewModel.getAssociationState().observeForever(mockAssociationStateObserver);
viewModel.getPairingCode().observeForever(mockPairingCodeObserver);
verify(mockAssociationStateObserver).onChanged(AssociationState.STARTED);
verify(mockPairingCodeObserver).onChanged(TEST_VERIFICATION_CODE);
}
@Test
public void startAssociation_associationCompleted() throws RemoteException {
StartAssociationResponse response =
new StartAssociationResponse(TEST_OOB_DATA, new byte[0], TEST_CAR_NAME);
viewModel.startAssociation();
captureAssociationCallback();
associationCallback.onAssociationStartSuccess(response);
associationCallback.onVerificationCodeAvailable(TEST_VERIFICATION_CODE);
associationCallback.onAssociationCompleted();
viewModel.getAssociationState().observeForever(mockAssociationStateObserver);
verify(mockAssociationStateObserver).onChanged(AssociationState.COMPLETED);
}
@Test
public void retryAssociation_retryWithPreviousIdentifier() {
ParcelUuid testIdentifier = new ParcelUuid(UUID.randomUUID());
viewModel.startAssociation(testIdentifier);
viewModel.retryAssociation();
verify(fakeConnector, times(2)).startAssociation(eq(testIdentifier), any());
}
@Test
public void stopAssociation() {
viewModel.startAssociation();
viewModel.stopAssociation();
viewModel.getAssociationState().observeForever(mockAssociationStateObserver);
verify(mockAssociationStateObserver).onChanged(AssociationState.NONE);
verify(fakeConnector).stopAssociation();
}
@Test
public void retrieveAssociatedDeviceForDriver() {
AssociatedDevice testDevice = createAssociatedDevice(/* isConnectionEnabled= */ true);
fakeConnector.addAssociatedDevice(testDevice);
AssociatedDeviceDetails expectedDetails =
new AssociatedDeviceDetails(testDevice, ConnectionState.NOT_DETECTED);
viewModel.getAssociatedDevicesDetails().observeForever(mockDeviceDetailsObserver);
assertThat(viewModel.getAssociatedDevicesDetails().getValue()).containsExactly(expectedDetails);
}
@Test
public void retrieveAssociatedDeviceForDriver_onConnection() {
// Adding devices before the creation of the ViewModel since the connector is connected
// upon construction.
AssociatedDevice driverDevice = createAssociatedDevice(/* isConnectionEnabled= */ true);
fakeConnector.addAssociatedDevice(driverDevice);
fakeConnector.claimAssociatedDevice(driverDevice.getDeviceId());
AssociatedDevice nonClaimedDevice = createAssociatedDevice(/* isConnectionEnabled= */ true);
fakeConnector.addAssociatedDevice(nonClaimedDevice);
viewModel =
new AssociatedDeviceViewModel(
application,
/* isSppEnabled= */ false,
TEST_BLE_DEVICE_NAME_PREFIX,
/* isPassengerEnabled= */ false,
fakeConnector);
adapter.enable();
AssociatedDeviceDetails expectedDetails =
new AssociatedDeviceDetails(driverDevice, ConnectionState.NOT_DETECTED);
viewModel.getAssociatedDevicesDetails().observeForever(mockDeviceDetailsObserver);
// Only the driver device should show up with passenger mode disabled.
assertThat(viewModel.getAssociatedDevicesDetails().getValue()).containsExactly(expectedDetails);
}
@Test
public void retrieveAssociatedDevices_onConnection_passengerEnabled() {
// Adding devices before the creation of the ViewModel since the connector is connected
// upon construction.
AssociatedDevice driverDevice = createAssociatedDevice(/* isConnectionEnabled= */ true);
fakeConnector.addAssociatedDevice(driverDevice);
fakeConnector.claimAssociatedDevice(driverDevice.getDeviceId());
AssociatedDevice nonClaimedDevice = createAssociatedDevice(/* isConnectionEnabled= */ true);
fakeConnector.addAssociatedDevice(nonClaimedDevice);
viewModel =
new AssociatedDeviceViewModel(
application,
/* isSppEnabled= */ false,
TEST_BLE_DEVICE_NAME_PREFIX,
/* isPassengerEnabled= */ true,
fakeConnector);
adapter.enable();
AssociatedDeviceDetails driverDetails =
new AssociatedDeviceDetails(driverDevice, ConnectionState.NOT_DETECTED);
AssociatedDeviceDetails passengerDetails =
new AssociatedDeviceDetails(nonClaimedDevice, ConnectionState.NOT_DETECTED);
viewModel.getAssociatedDevicesDetails().observeForever(mockDeviceDetailsObserver);
assertThat(viewModel.getAssociatedDevicesDetails().getValue()).containsExactly(
driverDetails, passengerDetails);
}
@Test
public void associatedDeviceUpdated() {
AssociatedDevice testDevice = createAssociatedDevice(/* isConnectionEnabled= */ true);
fakeConnector.addAssociatedDevice(testDevice);
AssociatedDevice updatedDevice =
new AssociatedDevice(
TEST_ASSOCIATED_DEVICE_ID,
TEST_ASSOCIATED_DEVICE_ADDRESS,
TEST_ASSOCIATED_DEVICE_NAME_2,
/* isConnectionEnabled= */ true);
AssociatedDeviceDetails expectedDetails =
new AssociatedDeviceDetails(updatedDevice, ConnectionState.NOT_DETECTED);
fakeConnector.getCallback().onAssociatedDeviceUpdated(updatedDevice);
viewModel.getAssociatedDevicesDetails().observeForever(mockDeviceDetailsObserver);
assertThat(viewModel.getAssociatedDevicesDetails().getValue()).containsExactly(expectedDetails);
}
@Test
public void removeAssociatedDevice() {
AssociatedDevice testDevice = createAssociatedDevice(/* isConnectionEnabled= */ true);
fakeConnector.addAssociatedDevice(testDevice);
viewModel.removeDevice(testDevice);
verify(fakeConnector).removeAssociatedDevice(eq(TEST_ASSOCIATED_DEVICE_ID));
}
@Test
public void associatedDeviceRemoved() {
AssociatedDevice testDevice = createAssociatedDevice(/* isConnectionEnabled= */ true);
fakeConnector.addAssociatedDevice(testDevice);
fakeConnector.removeAssociatedDevice(testDevice);
viewModel.getRemovedDevice().observeForever(mockRemovedDeviceObserver);
verify(mockRemovedDeviceObserver).onChanged(testDevice);
}
@Test
public void onDeviceConnected_updateConnectionStatusToDetected() {
AssociatedDevice testAssociatedDevice = createAssociatedDevice(/* isConnectionEnabled= */ true);
fakeConnector.addAssociatedDevice(testAssociatedDevice);
ConnectedDevice testConnectedDevice = createDetectedDevice();
when(fakeConnector.getConnectedDevices()).thenReturn(ImmutableList.of(testConnectedDevice));
fakeConnector.getCallback().onDeviceConnected(testConnectedDevice);
AssociatedDeviceDetails expectedDetails =
new AssociatedDeviceDetails(testAssociatedDevice, ConnectionState.DETECTED);
viewModel.getAssociatedDevicesDetails().observeForever(mockDeviceDetailsObserver);
assertThat(viewModel.getAssociatedDevicesDetails().getValue()).containsExactly(expectedDetails);
}
@Test
public void onSecureChannelEstablished_updatesConnectionStatusToConnected() {
AssociatedDevice testAssociatedDevice = createAssociatedDevice(/* isConnectionEnabled= */ true);
fakeConnector.addAssociatedDevice(testAssociatedDevice);
ConnectedDevice testConnectedDevice = createConnectedDevice();
when(fakeConnector.getConnectedDevices()).thenReturn(ImmutableList.of(testConnectedDevice));
fakeConnector.getCallback().onSecureChannelEstablished(testConnectedDevice);
AssociatedDeviceDetails expectedDetails =
new AssociatedDeviceDetails(testAssociatedDevice, ConnectionState.CONNECTED);
viewModel.getAssociatedDevicesDetails().observeForever(mockDeviceDetailsObserver);
assertThat(viewModel.getAssociatedDevicesDetails().getValue()).containsExactly(expectedDetails);
}
@Test
public void onDeviceDisconnected_updatesConnectionStatusToNotDetected() {
AssociatedDevice testAssociatedDevice = createAssociatedDevice(/* isConnectionEnabled= */ true);
fakeConnector.addAssociatedDevice(testAssociatedDevice);
ConnectedDevice testConnectedDevice = createConnectedDevice();
fakeConnector.getCallback().onDeviceDisconnected(testConnectedDevice);
AssociatedDeviceDetails expectedDetails =
new AssociatedDeviceDetails(testAssociatedDevice, ConnectionState.NOT_DETECTED);
viewModel.getAssociatedDevicesDetails().observeForever(mockDeviceDetailsObserver);
assertThat(viewModel.getAssociatedDevicesDetails().getValue()).containsExactly(expectedDetails);
}
@Test
public void onCleared_doesNotThrowBeforeConnectionEstablished() {
AssociatedDeviceViewModel associatedDeviceViewModel =
new AssociatedDeviceViewModel(
ApplicationProvider.getApplicationContext(),
/* isSppEnabled= */ false,
TEST_BLE_DEVICE_NAME_PREFIX,
/* isPassengerEnabled= */ false);
associatedDeviceViewModel.onCleared();
}
@Test
public void claimDevice_claimsAssociatedDevice() {
AssociatedDevice device = createAssociatedDevice(/* isConnectionEnabled= */ true);
fakeConnector.addAssociatedDevice(device);
viewModel.claimDevice(device);
verify(fakeConnector).claimAssociatedDevice(device.getDeviceId());
}
@Test
public void removeClaimOnDevice_removesAssociatedDeviceClaim() {
AssociatedDevice device = createAssociatedDevice(/* isConnectionEnabled= */ true);
fakeConnector.addAssociatedDevice(device);
viewModel.removeClaimOnDevice(device);
verify(fakeConnector).removeAssociatedDeviceClaim(device.getDeviceId());
}
private void captureAssociationCallback() {
ArgumentCaptor<IAssociationCallback> associationCallbackCaptor =
ArgumentCaptor.forClass(IAssociationCallback.class);
verify(fakeConnector).startAssociation(associationCallbackCaptor.capture());
associationCallback = associationCallbackCaptor.getValue();
}
private static AssociatedDevice createAssociatedDevice(boolean isConnectionEnabled) {
return new AssociatedDevice(
TEST_ASSOCIATED_DEVICE_ID,
TEST_ASSOCIATED_DEVICE_ADDRESS,
TEST_ASSOCIATED_DEVICE_NAME_1,
isConnectionEnabled);
}
private static ConnectedDevice createConnectedDevice() {
return new ConnectedDevice(
TEST_ASSOCIATED_DEVICE_ID,
TEST_ASSOCIATED_DEVICE_NAME_1,
/* belongsToDriver= */ true,
/* hasSecureChannel= */ true);
}
private static ConnectedDevice createDetectedDevice() {
return new ConnectedDevice(
TEST_ASSOCIATED_DEVICE_ID,
TEST_ASSOCIATED_DEVICE_NAME_1,
/* belongsToDriver= */ true,
/* hasSecureChannel= */ false);
}
}