[go: nahoru, domu]

blob: b383736ad9b6a027631bdc646a7be6fff9b9874a [file] [log] [blame]
/*
* Copyright 2021 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 androidx.car.app.hardware.info;
import static android.car.VehiclePropertyIds.DISTANCE_DISPLAY_UNITS;
import static android.car.VehiclePropertyIds.EV_BATTERY_LEVEL;
import static android.car.VehiclePropertyIds.EV_CHARGE_PORT_CONNECTED;
import static android.car.VehiclePropertyIds.EV_CHARGE_PORT_OPEN;
import static android.car.VehiclePropertyIds.FUEL_LEVEL;
import static android.car.VehiclePropertyIds.FUEL_LEVEL_LOW;
import static android.car.VehiclePropertyIds.FUEL_VOLUME_DISPLAY_UNITS;
import static android.car.VehiclePropertyIds.INFO_EV_BATTERY_CAPACITY;
import static android.car.VehiclePropertyIds.INFO_EV_CONNECTOR_TYPE;
import static android.car.VehiclePropertyIds.INFO_FUEL_CAPACITY;
import static android.car.VehiclePropertyIds.INFO_FUEL_TYPE;
import static android.car.VehiclePropertyIds.INFO_MAKE;
import static android.car.VehiclePropertyIds.INFO_MODEL;
import static android.car.VehiclePropertyIds.INFO_MODEL_YEAR;
import static android.car.VehiclePropertyIds.PERF_ODOMETER;
import static android.car.VehiclePropertyIds.PERF_VEHICLE_SPEED;
import static android.car.VehiclePropertyIds.PERF_VEHICLE_SPEED_DISPLAY;
import static android.car.VehiclePropertyIds.RANGE_REMAINING;
import static androidx.car.app.hardware.common.CarValue.STATUS_SUCCESS;
import static androidx.car.app.hardware.common.CarValue.STATUS_UNAVAILABLE;
import static androidx.car.app.hardware.common.CarValue.STATUS_UNKNOWN;
import static androidx.car.app.hardware.info.AutomotiveCarInfo.DEFAULT_SAMPLE_RATE;
import static androidx.car.app.hardware.info.AutomotiveCarInfo.SPEED_DISPLAY_UNIT_ID;
import static androidx.car.app.hardware.info.AutomotiveCarInfo.TOLL_CARD_STATUS_ID;
import static androidx.car.app.hardware.info.EnergyProfile.EVCONNECTOR_TYPE_CHADEMO;
import static androidx.car.app.hardware.info.EnergyProfile.FUEL_TYPE_UNLEADED;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.car.Car;
import android.car.hardware.property.CarPropertyManager;
import androidx.car.app.hardware.common.CarPropertyResponse;
import androidx.car.app.hardware.common.CarUnit;
import androidx.car.app.hardware.common.CarValue;
import androidx.car.app.hardware.common.CarZone;
import androidx.car.app.hardware.common.GetPropertyRequest;
import androidx.car.app.hardware.common.OnCarDataAvailableListener;
import androidx.car.app.hardware.common.OnCarPropertyResponseListener;
import androidx.car.app.hardware.common.PropertyManager;
import androidx.car.app.shadows.car.ShadowCar;
import com.google.common.collect.ImmutableMap;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.internal.DoNotInstrument;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference;
@RunWith(RobolectricTestRunner.class)
@Config(
manifest = Config.NONE,
shadows = {ShadowCar.class}
)
@DoNotInstrument
public class AutomotiveCarInfoTest {
private List<GetPropertyRequest> mGetPropertyRequests;
private List<Integer> mPropertyIds;
private List<CarPropertyResponse<?>> mResponse;
private CountDownLatch mCountDownLatch;
private final Executor mExecutor = directExecutor();
private AutomotiveCarInfo mAutomotiveCarInfo;
private List<CarZone> mCarZones;
@Mock
private Car mCarMock;
@Mock
private CarPropertyManager mCarPropertyManagerMock;
@Mock
private PropertyManager mPropertyManager;
private static final List<CarZone> GLOBAL_ZONE = Collections.singletonList(
CarZone.CAR_ZONE_GLOBAL);
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
ShadowCar.setCar(mCarMock);
when(mCarMock.getCarManager(anyString())).thenReturn(mCarPropertyManagerMock);
mAutomotiveCarInfo = new AutomotiveCarInfo(mPropertyManager);
mCountDownLatch = new CountDownLatch(1);
mGetPropertyRequests = new ArrayList<>();
mPropertyIds = new ArrayList<>();
mResponse = new ArrayList<>();
mCarZones = Arrays.asList(CarZone.CAR_ZONE_GLOBAL);
}
@Test
public void fetchModel_returnsModelWithUnknownValuesIfNoResponses()
throws InterruptedException {
// Add "INFO_MAKE", "INFO_MODEL" and "INFO_MODEL_YEAR" of the vehicle to the request.
mGetPropertyRequests.add(GetPropertyRequest.create(INFO_MAKE));
mGetPropertyRequests.add(GetPropertyRequest.create(INFO_MODEL));
mGetPropertyRequests.add(GetPropertyRequest.create(INFO_MODEL_YEAR));
when(mPropertyManager.submitGetPropertyRequest(
eq(mGetPropertyRequests), eq(mExecutor))).thenReturn(
Futures.immediateFuture(mResponse));
AtomicReference<Model> loadedResult = new AtomicReference<>();
OnCarDataAvailableListener<Model> listener = (data) -> {
loadedResult.set(data);
mCountDownLatch.countDown();
};
mAutomotiveCarInfo.fetchModel(mExecutor, listener);
mCountDownLatch.await();
assertThat(loadedResult.get()).isEqualTo(new Model.Builder().build());
verify(mPropertyManager).submitGetPropertyRequest(eq(mGetPropertyRequests), eq(mExecutor));
}
@Test
public void fetchModel_handlesResponsesWithDifferentStatuses() throws InterruptedException {
// Add "INFO_MAKE", "INFO_MODEL" and "INFO_MODEL_YEAR" of the vehicle to the request.
mGetPropertyRequests.add(GetPropertyRequest.create(INFO_MAKE));
mGetPropertyRequests.add(GetPropertyRequest.create(INFO_MODEL));
mGetPropertyRequests.add(GetPropertyRequest.create(INFO_MODEL_YEAR));
String testMake = "Toy Vehicle";
// Add "make", "model", "year" values to the response.
mResponse.add(CarPropertyResponse.builder().setPropertyId(INFO_MAKE).setStatus(
STATUS_SUCCESS).setValue(testMake).setTimestampMillis(1L).build());
mResponse.add(CarPropertyResponse.builder().setPropertyId(INFO_MODEL).setStatus(
STATUS_UNAVAILABLE).setTimestampMillis(2L).build());
mResponse.add(CarPropertyResponse.builder().setPropertyId(INFO_MODEL_YEAR).setStatus(
CarValue.STATUS_UNKNOWN).setTimestampMillis(3L).build());
when(mPropertyManager.submitGetPropertyRequest(eq(mGetPropertyRequests),
eq(mExecutor))).thenReturn(Futures.immediateFuture(mResponse));
AtomicReference<Model> loadedResult = new AtomicReference<>();
OnCarDataAvailableListener<Model> listener = (data) -> {
loadedResult.set(data);
mCountDownLatch.countDown();
};
mAutomotiveCarInfo.fetchModel(mExecutor, listener);
mCountDownLatch.await();
assertThat(loadedResult.get()).isEqualTo(new Model.Builder().setManufacturer(
new CarValue<>(testMake, 1, CarValue.STATUS_SUCCESS)).setName(
new CarValue<>(null, 2, CarValue.STATUS_UNAVAILABLE)).setYear(
new CarValue<>(null, 3, CarValue.STATUS_UNKNOWN)).build());
verify(mPropertyManager, times(1)).submitGetPropertyRequest(eq(mGetPropertyRequests),
eq(mExecutor));
}
@Test
public void getModel_SuccessfulResponse() throws InterruptedException {
// Add "INFO_MAKE", "INFO_MODEL" and "INFO_MODEL_YEAR" of the vehicle to the request.
mGetPropertyRequests.add(GetPropertyRequest.create(INFO_MAKE));
mGetPropertyRequests.add(GetPropertyRequest.create(INFO_MODEL));
mGetPropertyRequests.add(GetPropertyRequest.create(INFO_MODEL_YEAR));
// Add "make", "model", "year" values to the response.
mResponse.add(CarPropertyResponse.builder().setPropertyId(INFO_MAKE).setStatus(
STATUS_SUCCESS).setValue("Toy Vehicle").setTimestampMillis(1L).build());
mResponse.add(CarPropertyResponse.builder().setPropertyId(INFO_MODEL).setStatus(
STATUS_SUCCESS).setValue("Speedy Model").setTimestampMillis(2L).build());
mResponse.add(CarPropertyResponse.builder().setPropertyId(INFO_MODEL_YEAR).setStatus(
STATUS_SUCCESS).setValue(2020).setTimestampMillis(3L).build());
ListenableFuture<List<CarPropertyResponse<?>>> listenableCarPropertyResponse =
Futures.immediateFuture(mResponse);
when(mPropertyManager.submitGetPropertyRequest(eq(mGetPropertyRequests),
eq(mExecutor))).thenReturn(listenableCarPropertyResponse);
AtomicReference<Model> loadedResult = new AtomicReference<>();
OnCarDataAvailableListener<Model> listener = (data) -> {
loadedResult.set(data);
mCountDownLatch.countDown();
};
mAutomotiveCarInfo.fetchModel(mExecutor, listener);
verify(mPropertyManager, times(1)).submitGetPropertyRequest(eq(mGetPropertyRequests),
eq(mExecutor));
mCountDownLatch.await();
Model mModel = loadedResult.get();
assertThat(mModel.getName().getValue()).isEqualTo("Speedy Model");
assertThat(mModel.getManufacturer().getValue()).isEqualTo("Toy Vehicle");
assertThat(mModel.getYear().getValue()).isEqualTo(2020);
assertThat(mModel.getManufacturer().getTimestampMillis()).isEqualTo(1);
assertThat(mModel.getName().getTimestampMillis()).isEqualTo(2);
// test CarZone
assertThat(mModel.getName().getCarZones()).isEqualTo(GLOBAL_ZONE);
assertThat(mModel.getManufacturer().getCarZones()).isEqualTo(GLOBAL_ZONE);
assertThat(mModel.getYear().getCarZones()).isEqualTo(GLOBAL_ZONE);
assertThat(mModel.getYear().getTimestampMillis()).isEqualTo(3);
}
@Test
public void getModel_MoreResponsesThanRequestsFailure() throws InterruptedException {
// Add "INFO_MAKE" and "INFO_MODEL" of the vehicle to the request.
mGetPropertyRequests.add(GetPropertyRequest.create(INFO_MAKE));
mGetPropertyRequests.add(GetPropertyRequest.create(INFO_MODEL));
// Add "make", "model", "year" values to the response.
mResponse.add(CarPropertyResponse.builder().setPropertyId(INFO_MAKE).setStatus(
STATUS_SUCCESS).setValue("Toy Vehicle").setTimestampMillis(1L).build());
mResponse.add(CarPropertyResponse.builder().setPropertyId(INFO_MODEL).setStatus(
STATUS_SUCCESS).setValue("Speedy Model").setTimestampMillis(2L).build());
mResponse.add(CarPropertyResponse.builder().setPropertyId(INFO_MODEL_YEAR).setStatus(
STATUS_SUCCESS).setValue(2020).setTimestampMillis(3L).build());
ListenableFuture<List<CarPropertyResponse<?>>> listenableCarPropertyResponse =
Futures.immediateFuture(mResponse);
when(mPropertyManager.submitGetPropertyRequest(
eq(mGetPropertyRequests), eq(mExecutor))).thenReturn(listenableCarPropertyResponse);
OnCarDataAvailableListener<Model> listener = (data) -> {
};
// Given that the number of values in the response is more(3) than what was requested(2),
// there should be null pointer exception.
assertThrows(NullPointerException.class,
() -> mAutomotiveCarInfo.fetchModel(mExecutor, listener));
}
@Test
public void getEnergyProfile_SuccessfulResponse() throws InterruptedException {
// chademo in car service
int chademoInVehicle = 4;
// Add "INFO_EV_CONNECTOR_TYPE" and "INFO_FUEL_TYPE" type of the vehicle to the request.
mGetPropertyRequests.add(GetPropertyRequest.create(INFO_EV_CONNECTOR_TYPE));
mGetPropertyRequests.add(GetPropertyRequest.create(INFO_FUEL_TYPE));
// Add "evConnector" and "fuel" type of the vehicle to the response.
mResponse.add(CarPropertyResponse.builder().setPropertyId(INFO_EV_CONNECTOR_TYPE).setStatus(
STATUS_SUCCESS).setValue(new Integer[]{chademoInVehicle}).setTimestampMillis(
1L).build());
mResponse.add(CarPropertyResponse.builder().setPropertyId(INFO_FUEL_TYPE).setStatus(
STATUS_SUCCESS).setValue(new Integer[]{FUEL_TYPE_UNLEADED}).setTimestampMillis(
2L).build());
ListenableFuture<List<CarPropertyResponse<?>>> listenableCarPropertyResponse =
Futures.immediateFuture(mResponse);
when(mPropertyManager.submitGetPropertyRequest(eq(mGetPropertyRequests),
eq(mExecutor))).thenReturn(listenableCarPropertyResponse);
AtomicReference<EnergyProfile> loadedResult = new AtomicReference<>();
OnCarDataAvailableListener<EnergyProfile> listener = (data) -> {
loadedResult.set(data);
mCountDownLatch.countDown();
};
mAutomotiveCarInfo.fetchEnergyProfile(mExecutor, listener);
verify(mPropertyManager, times(1)).submitGetPropertyRequest(eq(mGetPropertyRequests),
eq(mExecutor));
mCountDownLatch.await();
EnergyProfile energyProfile = loadedResult.get();
List<Integer> evConnector = new ArrayList<Integer>();
evConnector.add(EVCONNECTOR_TYPE_CHADEMO);
List<Integer> fuel = new ArrayList<Integer>();
fuel.add(FUEL_TYPE_UNLEADED);
assertThat(energyProfile.getEvConnectorTypes().getValue()).isEqualTo(evConnector);
assertThat(energyProfile.getFuelTypes().getValue()).isEqualTo(fuel);
}
@Test
public void fetchEnergyProfile_handlesResponsesWithDifferentStatuses()
throws InterruptedException {
// Add "INFO_EV_CONNECTOR_TYPE" and "INFO_FUEL_TYPE" type of the vehicle to the request.
mGetPropertyRequests.add(GetPropertyRequest.create(INFO_EV_CONNECTOR_TYPE));
mGetPropertyRequests.add(GetPropertyRequest.create(INFO_FUEL_TYPE));
// Add "evConnector" and "fuel" type of the vehicle to the response.
mResponse.add(CarPropertyResponse.builder().setPropertyId(INFO_EV_CONNECTOR_TYPE).setStatus(
STATUS_UNAVAILABLE).setTimestampMillis(1L).build());
mResponse.add(CarPropertyResponse.builder().setPropertyId(INFO_FUEL_TYPE).setStatus(
CarValue.STATUS_UNKNOWN).setTimestampMillis(2L).build());
when(mPropertyManager.submitGetPropertyRequest(eq(mGetPropertyRequests),
eq(mExecutor))).thenReturn(Futures.immediateFuture(mResponse));
AtomicReference<EnergyProfile> loadedResult = new AtomicReference<>();
OnCarDataAvailableListener<EnergyProfile> listener = (data) -> {
loadedResult.set(data);
mCountDownLatch.countDown();
};
mAutomotiveCarInfo.fetchEnergyProfile(mExecutor, listener);
mCountDownLatch.await();
assertThat(loadedResult.get()).isEqualTo(new EnergyProfile.Builder().setEvConnectorTypes(
new CarValue<>(null, 1, STATUS_UNAVAILABLE)).setFuelTypes(
new CarValue<>(null, 2, STATUS_UNKNOWN)).build());
verify(mPropertyManager, times(1)).submitGetPropertyRequest(eq(mGetPropertyRequests),
eq(mExecutor));
}
@Test
public void fetchEnergyProfile_returnsEnergyProfileWithUnknownValuesIfNoResponses()
throws InterruptedException {
// Add "INFO_EV_CONNECTOR_TYPE" and "INFO_FUEL_TYPE" type of the vehicle to the request.
mGetPropertyRequests.add(GetPropertyRequest.create(INFO_EV_CONNECTOR_TYPE));
mGetPropertyRequests.add(GetPropertyRequest.create(INFO_FUEL_TYPE));
// Leave the response empty.
when(mPropertyManager.submitGetPropertyRequest(eq(mGetPropertyRequests),
eq(mExecutor))).thenReturn(
Futures.immediateFuture(mResponse));
AtomicReference<EnergyProfile> loadedResult = new AtomicReference<>();
OnCarDataAvailableListener<EnergyProfile> listener = (data) -> {
loadedResult.set(data);
mCountDownLatch.countDown();
};
mAutomotiveCarInfo.fetchEnergyProfile(mExecutor, listener);
mCountDownLatch.await();
assertThat(loadedResult.get()).isEqualTo(new EnergyProfile.Builder().build());
verify(mPropertyManager, times(1)).submitGetPropertyRequest(
eq(mGetPropertyRequests), eq(mExecutor));
}
@Test
public void getMileage_verifyResponse() throws InterruptedException {
// VehicleUnit.METER in car service
int meterUnit = 0x21;
// Create "PERF_ODOMETER" and "DISTANCE_DISPLAY_UNITS" property IDs list with car zones.
Map<Integer, List<CarZone>> propertyIdsWithCarZones =
ImmutableMap.<Integer, List<CarZone>>builder()
.put(PERF_ODOMETER, mCarZones)
.put(DISTANCE_DISPLAY_UNITS, mCarZones)
.buildKeepingLast();
AtomicReference<Mileage> loadedResult = new AtomicReference<>();
OnCarDataAvailableListener<Mileage> listener = (data) -> {
loadedResult.set(data);
mCountDownLatch.countDown();
};
mAutomotiveCarInfo.addMileageListener(mExecutor, listener);
ArgumentCaptor<OnCarPropertyResponseListener> captor = ArgumentCaptor.forClass(
OnCarPropertyResponseListener.class);
verify(mPropertyManager).submitRegisterListenerRequest(eq(propertyIdsWithCarZones),
eq(DEFAULT_SAMPLE_RATE), captor.capture(), eq(mExecutor));
mResponse.add(CarPropertyResponse.builder().setPropertyId(PERF_ODOMETER).setStatus(
STATUS_SUCCESS).setValue(1f).setTimestampMillis(1L).build());
mResponse.add(CarPropertyResponse.builder().setPropertyId(DISTANCE_DISPLAY_UNITS).setStatus(
STATUS_SUCCESS).setValue(meterUnit).setTimestampMillis(2L).build());
captor.getValue().onCarPropertyResponses(mResponse);
mCountDownLatch.await();
Mileage mileage = loadedResult.get();
assertThat(mileage.getOdometerMeters().getValue()).isEqualTo(1f);
assertThat(mileage.getDistanceDisplayUnit().getValue()).isEqualTo(2);
}
@Test
public void addMileageListener_handlesResponsesWithDifferentStatuses()
throws InterruptedException {
// Create "PERF_ODOMETER" and "DISTANCE_DISPLAY_UNITS" property IDs list with car zones.
Map<Integer, List<CarZone>> propertyIdsWithCarZones =
ImmutableMap.<Integer, List<CarZone>>builder().put(PERF_ODOMETER, mCarZones).put(
DISTANCE_DISPLAY_UNITS, mCarZones).buildKeepingLast();
AtomicReference<Mileage> loadedResult = new AtomicReference<>();
OnCarDataAvailableListener<Mileage> listener = (data) -> {
loadedResult.set(data);
mCountDownLatch.countDown();
};
mAutomotiveCarInfo.addMileageListener(mExecutor, listener);
ArgumentCaptor<OnCarPropertyResponseListener> captor = ArgumentCaptor.forClass(
OnCarPropertyResponseListener.class);
verify(mPropertyManager).submitRegisterListenerRequest(eq(propertyIdsWithCarZones),
eq(DEFAULT_SAMPLE_RATE), captor.capture(), eq(mExecutor));
mResponse.add(CarPropertyResponse.builder().setPropertyId(PERF_ODOMETER).setStatus(
STATUS_UNAVAILABLE).setTimestampMillis(1L).build());
mResponse.add(CarPropertyResponse.builder().setPropertyId(DISTANCE_DISPLAY_UNITS).setStatus(
STATUS_UNKNOWN).setTimestampMillis(2L).build());
captor.getValue().onCarPropertyResponses(mResponse);
mCountDownLatch.await();
assertThat(loadedResult.get()).isEqualTo(new Mileage.Builder().setOdometerMeters(
new CarValue<>(null, 1, STATUS_UNAVAILABLE)).setDistanceDisplayUnit(
new CarValue<>(null, 2, STATUS_UNKNOWN)).build());
}
@Test
public void addMileageListener_returnsMileageWithUnknownValuesIfNoResponses()
throws InterruptedException {
// Create "PERF_ODOMETER" and "DISTANCE_DISPLAY_UNITS" property IDs list with car zones.
Map<Integer, List<CarZone>> propertyIdsWithCarZones =
ImmutableMap.<Integer, List<CarZone>>builder().put(PERF_ODOMETER, mCarZones).put(
DISTANCE_DISPLAY_UNITS, mCarZones).buildKeepingLast();
AtomicReference<Mileage> loadedResult = new AtomicReference<>();
OnCarDataAvailableListener<Mileage> listener = (data) -> {
loadedResult.set(data);
mCountDownLatch.countDown();
};
mAutomotiveCarInfo.addMileageListener(mExecutor, listener);
ArgumentCaptor<OnCarPropertyResponseListener> captor = ArgumentCaptor.forClass(
OnCarPropertyResponseListener.class);
verify(mPropertyManager).submitRegisterListenerRequest(eq(propertyIdsWithCarZones),
eq(DEFAULT_SAMPLE_RATE), captor.capture(), eq(mExecutor));
captor.getValue().onCarPropertyResponses(mResponse);
mCountDownLatch.await();
assertThat(loadedResult.get()).isEqualTo(new Mileage.Builder().build());
}
@Test
public void getMileage_multiplRequestsSameListener() throws InterruptedException {
// VehicleUnit.METER in car service
int meterUnit = 0x21;
// Create "PERF_ODOMETER" and "DISTANCE_DISPLAY_UNITS" property IDs list with car zones.
Map<Integer, List<CarZone>> propertyIdsWithCarZones =
ImmutableMap.<Integer, List<CarZone>>builder()
.put(PERF_ODOMETER, mCarZones)
.put(DISTANCE_DISPLAY_UNITS, mCarZones)
.buildKeepingLast();
AtomicReference<Mileage> loadedResult = new AtomicReference<>();
OnCarDataAvailableListener<Mileage> listener = (data) -> {
loadedResult.set(data);
mCountDownLatch.countDown();
};
mAutomotiveCarInfo.addMileageListener(mExecutor, listener);
mAutomotiveCarInfo.addMileageListener(mExecutor, listener);
ArgumentCaptor<OnCarPropertyResponseListener> captor = ArgumentCaptor.forClass(
OnCarPropertyResponseListener.class);
verify(mPropertyManager, times(2)).submitRegisterListenerRequest(
eq(propertyIdsWithCarZones), eq(DEFAULT_SAMPLE_RATE), captor.capture(),
eq(mExecutor));
mResponse.add(CarPropertyResponse.builder().setPropertyId(PERF_ODOMETER).setStatus(
STATUS_SUCCESS).setValue(1f).setTimestampMillis(1L).build());
mResponse.add(CarPropertyResponse.builder().setPropertyId(DISTANCE_DISPLAY_UNITS).setStatus(
STATUS_SUCCESS).setValue(meterUnit).setTimestampMillis(2L).build());
captor.getValue().onCarPropertyResponses(mResponse);
mCountDownLatch.await();
Mileage mileage = loadedResult.get();
assertThat(mileage.getOdometerMeters().getValue()).isEqualTo(1f);
assertThat(mileage.getDistanceDisplayUnit().getValue()).isEqualTo(2);
}
@Test
public void getMileage_multipleRequestsDifferentListener() throws InterruptedException {
// VehicleUnit.METER in car service
int meterUnit = 0x21;
CountDownLatch firstCountDownLatch = new CountDownLatch(1);
CountDownLatch secondCountDownLatch = new CountDownLatch(1);
Executor firstExecutor = directExecutor();
Executor secondExecutor = directExecutor();
// Create "PERF_ODOMETER" and "DISTANCE_DISPLAY_UNITS" property IDs list with car zones.
Map<Integer, List<CarZone>> propertyIdsWithCarZones =
ImmutableMap.<Integer, List<CarZone>>builder()
.put(PERF_ODOMETER, mCarZones)
.put(DISTANCE_DISPLAY_UNITS, mCarZones)
.buildKeepingLast();
AtomicReference<Mileage> loadedFirstResult = new AtomicReference<>();
AtomicReference<Mileage> loadedSecondResult = new AtomicReference<>();
OnCarDataAvailableListener<Mileage> firstListener = (data) -> {
loadedFirstResult.set(data);
firstCountDownLatch.countDown();
};
OnCarDataAvailableListener<Mileage> secondListener = (data) -> {
loadedSecondResult.set(data);
secondCountDownLatch.countDown();
};
// Send request for first listener.
mAutomotiveCarInfo.addMileageListener(firstExecutor, firstListener);
ArgumentCaptor<OnCarPropertyResponseListener> firstCaptor = ArgumentCaptor.forClass(
OnCarPropertyResponseListener.class);
verify(mPropertyManager, times(1)).submitRegisterListenerRequest(
eq(propertyIdsWithCarZones), eq(DEFAULT_SAMPLE_RATE), firstCaptor.capture(),
eq(firstExecutor));
mResponse.add(CarPropertyResponse.builder().setPropertyId(PERF_ODOMETER).setStatus(
STATUS_SUCCESS).setValue(1f).setTimestampMillis(1L).build());
mResponse.add(CarPropertyResponse.builder().setPropertyId(DISTANCE_DISPLAY_UNITS).setStatus(
STATUS_SUCCESS).setValue(meterUnit).setTimestampMillis(2L).build());
firstCaptor.getValue().onCarPropertyResponses(mResponse);
// Send request for second listener.
mAutomotiveCarInfo.addMileageListener(secondExecutor, secondListener);
ArgumentCaptor<OnCarPropertyResponseListener> secondCaptor = ArgumentCaptor.forClass(
OnCarPropertyResponseListener.class);
// Listener request would be submitted twice by now.
verify(mPropertyManager, times(2)).submitRegisterListenerRequest(
eq(propertyIdsWithCarZones), eq(DEFAULT_SAMPLE_RATE), secondCaptor.capture(),
eq(secondExecutor));
secondCaptor.getValue().onCarPropertyResponses(mResponse);
firstCountDownLatch.await();
Mileage firstMileage = loadedFirstResult.get();
assertThat(firstMileage.getOdometerMeters().getValue()).isEqualTo(1f);
assertThat(firstMileage.getDistanceDisplayUnit().getValue()).isEqualTo(2);
secondCountDownLatch.await();
Mileage secondMileage = loadedSecondResult.get();
assertThat(secondMileage.getOdometerMeters().getValue()).isEqualTo(1f);
assertThat(secondMileage.getDistanceDisplayUnit().getValue()).isEqualTo(2);
}
@Test
public void getEvStatus_verifyResponse() throws InterruptedException {
// Create "EV_CHARGE_PORT_OPEN" and "EV_CHARGE_PORT_CONNECTED" property IDs list with car
// zones.
Map<Integer, List<CarZone>> propertyIdsWithCarZones =
ImmutableMap.<Integer, List<CarZone>>builder()
.put(EV_CHARGE_PORT_OPEN, mCarZones)
.put(EV_CHARGE_PORT_CONNECTED, mCarZones)
.buildKeepingLast();
AtomicReference<EvStatus> loadedResult = new AtomicReference<>();
OnCarDataAvailableListener<EvStatus> listener = (data) -> {
loadedResult.set(data);
mCountDownLatch.countDown();
};
mAutomotiveCarInfo.addEvStatusListener(mExecutor, listener);
ArgumentCaptor<OnCarPropertyResponseListener> captor = ArgumentCaptor.forClass(
OnCarPropertyResponseListener.class);
verify(mPropertyManager).submitRegisterListenerRequest(eq(propertyIdsWithCarZones),
eq(DEFAULT_SAMPLE_RATE), captor.capture(), eq(mExecutor));
mResponse.add(CarPropertyResponse.builder().setPropertyId(EV_CHARGE_PORT_OPEN).setStatus(
STATUS_SUCCESS).setValue(true).setTimestampMillis(1L).build());
mResponse.add(CarPropertyResponse.builder().setPropertyId(
EV_CHARGE_PORT_CONNECTED).setStatus(STATUS_SUCCESS).setValue(
false).setTimestampMillis(2L).build());
captor.getValue().onCarPropertyResponses(mResponse);
mCountDownLatch.await();
EvStatus evStatus = loadedResult.get();
assertThat(evStatus.getEvChargePortOpen().getValue()).isEqualTo(true);
assertThat(evStatus.getEvChargePortConnected().getValue()).isEqualTo(false);
}
@Test
public void addEvStatusListener_handlesResponsesWithDifferentStatuses()
throws InterruptedException {
// Create "EV_CHARGE_PORT_OPEN" and "EV_CHARGE_PORT_CONNECTED" property IDs list with car
// zones.
Map<Integer, List<CarZone>> propertyIdsWithCarZones =
ImmutableMap.<Integer, List<CarZone>>builder().put(EV_CHARGE_PORT_OPEN,
mCarZones).put(EV_CHARGE_PORT_CONNECTED, mCarZones).buildKeepingLast();
AtomicReference<EvStatus> loadedResult = new AtomicReference<>();
OnCarDataAvailableListener<EvStatus> listener = (data) -> {
loadedResult.set(data);
mCountDownLatch.countDown();
};
mAutomotiveCarInfo.addEvStatusListener(mExecutor, listener);
ArgumentCaptor<OnCarPropertyResponseListener> captor = ArgumentCaptor.forClass(
OnCarPropertyResponseListener.class);
verify(mPropertyManager).submitRegisterListenerRequest(eq(propertyIdsWithCarZones),
eq(DEFAULT_SAMPLE_RATE), captor.capture(), eq(mExecutor));
mResponse.add(CarPropertyResponse.builder().setPropertyId(EV_CHARGE_PORT_OPEN).setStatus(
STATUS_UNKNOWN).setTimestampMillis(1L).build());
mResponse.add(CarPropertyResponse.builder().setPropertyId(
EV_CHARGE_PORT_CONNECTED).setStatus(STATUS_UNAVAILABLE).setTimestampMillis(
2L).build());
captor.getValue().onCarPropertyResponses(mResponse);
mCountDownLatch.await();
assertThat(loadedResult.get()).isEqualTo(new EvStatus.Builder().setEvChargePortOpen(
new CarValue<>(null, 1, STATUS_UNKNOWN)).setEvChargePortConnected(
new CarValue<>(null, 2, STATUS_UNAVAILABLE)).build());
}
@Test
public void addEvStatusListener_returnsEvStatusWithUnknownValuesIfNoResponses()
throws InterruptedException {
// Create "EV_CHARGE_PORT_OPEN" and "EV_CHARGE_PORT_CONNECTED" property IDs list with car
// zones.
Map<Integer, List<CarZone>> propertyIdsWithCarZones =
ImmutableMap.<Integer, List<CarZone>>builder().put(EV_CHARGE_PORT_OPEN,
mCarZones).put(EV_CHARGE_PORT_CONNECTED, mCarZones)
.buildKeepingLast();
AtomicReference<EvStatus> loadedResult = new AtomicReference<>();
OnCarDataAvailableListener<EvStatus> listener = (data) -> {
loadedResult.set(data);
mCountDownLatch.countDown();
};
mAutomotiveCarInfo.addEvStatusListener(mExecutor, listener);
ArgumentCaptor<OnCarPropertyResponseListener> captor = ArgumentCaptor.forClass(
OnCarPropertyResponseListener.class);
verify(mPropertyManager).submitRegisterListenerRequest(eq(propertyIdsWithCarZones),
eq(DEFAULT_SAMPLE_RATE), captor.capture(), eq(mExecutor));
captor.getValue().onCarPropertyResponses(mResponse);
mCountDownLatch.await();
assertThat(loadedResult.get()).isEqualTo(new EvStatus.Builder().build());
}
@Test
public void getEvStatus_withInvalidResponse_verifyResponse() throws InterruptedException {
// Create "EV_CHARGE_PORT_OPEN" and "EV_CHARGE_PORT_CONNECTED" property IDs list with car
// zones.
Map<Integer, List<CarZone>> propertyIdsWithCarZones =
ImmutableMap.<Integer, List<CarZone>>builder()
.put(EV_CHARGE_PORT_OPEN, mCarZones)
.put(EV_CHARGE_PORT_CONNECTED, mCarZones)
.buildKeepingLast();
AtomicReference<EvStatus> loadedResult = new AtomicReference<>();
OnCarDataAvailableListener<EvStatus> listener = (data) -> {
loadedResult.set(data);
mCountDownLatch.countDown();
};
mAutomotiveCarInfo.addEvStatusListener(mExecutor, listener);
ArgumentCaptor<OnCarPropertyResponseListener> captor = ArgumentCaptor.forClass(
OnCarPropertyResponseListener.class);
verify(mPropertyManager).submitRegisterListenerRequest(eq(propertyIdsWithCarZones),
eq(DEFAULT_SAMPLE_RATE), captor.capture(), eq(mExecutor));
mResponse.add(CarPropertyResponse.builder()
.setPropertyId(EV_CHARGE_PORT_OPEN)
.setStatus(STATUS_SUCCESS)
.setValue(true)
.setTimestampMillis(1L).build());
mResponse.add(CarPropertyResponse.builder().setPropertyId(
EV_CHARGE_PORT_CONNECTED).setStatus(CarValue.STATUS_UNIMPLEMENTED).setValue(
null).setTimestampMillis(1L).build());
captor.getValue().onCarPropertyResponses(mResponse);
mCountDownLatch.await();
EvStatus evStatus = loadedResult.get();
assertThat(evStatus.getEvChargePortOpen().getValue()).isEqualTo(true);
assertThat(evStatus.getEvChargePortConnected().getStatus()).isEqualTo(
CarValue.STATUS_UNIMPLEMENTED);
}
@Config(minSdk = 31)
@Test
public void getTollCard_verifyResponseApi31() throws InterruptedException {
// Create "TOLL_CARD_STATUS_ID" request property IDs list with car zones.
Map<Integer, List<CarZone>> propertyIdsWithCarZones =
ImmutableMap.<Integer, List<CarZone>>builder()
.put(TOLL_CARD_STATUS_ID, mCarZones)
.buildKeepingLast();
AtomicReference<TollCard> loadedResult = new AtomicReference<>();
OnCarDataAvailableListener<TollCard> listener = (data) -> {
loadedResult.set(data);
mCountDownLatch.countDown();
};
mAutomotiveCarInfo.addTollListener(mExecutor, listener);
ArgumentCaptor<OnCarPropertyResponseListener> captor = ArgumentCaptor.forClass(
OnCarPropertyResponseListener.class);
verify(mPropertyManager).submitRegisterListenerRequest(eq(propertyIdsWithCarZones),
eq(DEFAULT_SAMPLE_RATE), captor.capture(), eq(mExecutor));
mResponse.add(CarPropertyResponse.builder().setPropertyId(TOLL_CARD_STATUS_ID).setStatus(
STATUS_SUCCESS).setValue(TollCard.TOLLCARD_STATE_VALID).setTimestampMillis(
1L).build());
captor.getValue().onCarPropertyResponses(mResponse);
mCountDownLatch.await();
TollCard tollCard = loadedResult.get();
assertThat(tollCard.getCardState().getValue()).isEqualTo(TollCard.TOLLCARD_STATE_VALID);
assertThat(tollCard.getCardState().getCarZones()).isEqualTo(GLOBAL_ZONE);
}
@Config(maxSdk = 30)
@Test
public void getTollCard_verifyResponseApi30() {
AtomicReference<TollCard> loadedResult = new AtomicReference<>();
OnCarDataAvailableListener<TollCard> listener = (data) -> {
loadedResult.set(data);
mCountDownLatch.countDown();
};
mAutomotiveCarInfo.addTollListener(mExecutor, listener);
TollCard tollCard = loadedResult.get();
assertThat(tollCard.getCardState().getStatus()).isEqualTo(CarValue.STATUS_UNIMPLEMENTED);
assertThat(tollCard.getCardState().getCarZones().isEmpty()).isTrue();
}
@Test
public void getSpeed_verifyResponse() throws InterruptedException {
// Create "PERF_VEHICLE_SPEED", "PERF_VEHICLE_SPEED_DISPLAY" and "SPEED_DISPLAY_UNIT_ID"
// property IDs list with car zones.
Map<Integer, List<CarZone>> propertyIdsWithCarZones =
ImmutableMap.<Integer, List<CarZone>>builder()
.put(PERF_VEHICLE_SPEED, mCarZones)
.put(PERF_VEHICLE_SPEED_DISPLAY, mCarZones)
.put(SPEED_DISPLAY_UNIT_ID, mCarZones)
.buildKeepingLast();
float defaultSpeed = 20f;
float defaultRawSpeed = 20.5f;
// VehicleUnit.METER_PER_SEC in car service
int metersPerSec = 0x01;
AtomicReference<Speed> loadedResult = new AtomicReference<>();
OnCarDataAvailableListener<Speed> listener = (data) -> {
loadedResult.set(data);
mCountDownLatch.countDown();
};
mAutomotiveCarInfo.addSpeedListener(mExecutor, listener);
ArgumentCaptor<OnCarPropertyResponseListener> captor = ArgumentCaptor.forClass(
OnCarPropertyResponseListener.class);
verify(mPropertyManager).submitRegisterListenerRequest(eq(propertyIdsWithCarZones),
eq(DEFAULT_SAMPLE_RATE), captor.capture(), eq(mExecutor));
mResponse.add(CarPropertyResponse.builder().setPropertyId(SPEED_DISPLAY_UNIT_ID).setStatus(
STATUS_SUCCESS).setValue(metersPerSec).setTimestampMillis(1L).build());
mResponse.add(CarPropertyResponse.builder().setPropertyId(PERF_VEHICLE_SPEED).setStatus(
STATUS_SUCCESS).setValue(defaultRawSpeed).setTimestampMillis(2L).build());
mResponse.add(CarPropertyResponse.builder().setPropertyId(
PERF_VEHICLE_SPEED_DISPLAY).setStatus(STATUS_SUCCESS).setValue(
defaultSpeed).setTimestampMillis(3L).build());
captor.getValue().onCarPropertyResponses(mResponse);
mCountDownLatch.await();
Speed speed = loadedResult.get();
assertThat(speed.getRawSpeedMetersPerSecond().getValue()).isEqualTo(defaultRawSpeed);
assertThat(speed.getDisplaySpeedMetersPerSecond().getValue()).isEqualTo(defaultSpeed);
assertThat(speed.getSpeedDisplayUnit().getValue()).isEqualTo(CarUnit.METERS_PER_SEC);
}
@Test
public void addSpeedListener_handlesResponsesWithDifferentStatuses()
throws InterruptedException {
// Create "PERF_VEHICLE_SPEED", "PERF_VEHICLE_SPEED_DISPLAY" and "SPEED_DISPLAY_UNIT_ID"
// property IDs list with car zones.
Map<Integer, List<CarZone>> propertyIdsWithCarZones =
ImmutableMap.<Integer, List<CarZone>>builder().put(PERF_VEHICLE_SPEED,
mCarZones).put(PERF_VEHICLE_SPEED_DISPLAY, mCarZones).put(
SPEED_DISPLAY_UNIT_ID, mCarZones).buildKeepingLast();
AtomicReference<Speed> loadedResult = new AtomicReference<>();
OnCarDataAvailableListener<Speed> listener = (data) -> {
loadedResult.set(data);
mCountDownLatch.countDown();
};
mAutomotiveCarInfo.addSpeedListener(mExecutor, listener);
ArgumentCaptor<OnCarPropertyResponseListener> captor = ArgumentCaptor.forClass(
OnCarPropertyResponseListener.class);
verify(mPropertyManager).submitRegisterListenerRequest(eq(propertyIdsWithCarZones),
eq(DEFAULT_SAMPLE_RATE), captor.capture(), eq(mExecutor));
mResponse.add(CarPropertyResponse.builder().setPropertyId(PERF_VEHICLE_SPEED).setStatus(
STATUS_UNAVAILABLE).setTimestampMillis(1L).build());
mResponse.add(CarPropertyResponse.builder().setPropertyId(
PERF_VEHICLE_SPEED_DISPLAY).setStatus(STATUS_UNKNOWN).setTimestampMillis(
2L).build());
mResponse.add(CarPropertyResponse.builder().setPropertyId(SPEED_DISPLAY_UNIT_ID).setStatus(
STATUS_SUCCESS).setTimestampMillis(3L).setValue(/*VehicleUnit.METER_PER_SEC=*/
0x01).build());
captor.getValue().onCarPropertyResponses(mResponse);
mCountDownLatch.await();
assertThat(loadedResult.get()).isEqualTo(new Speed.Builder().setRawSpeedMetersPerSecond(
new CarValue<>(null, 1, STATUS_UNAVAILABLE)).setDisplaySpeedMetersPerSecond(
new CarValue<>(null, 2, STATUS_UNKNOWN)).setSpeedDisplayUnit(
new CarValue<>(CarUnit.METERS_PER_SEC, 3, STATUS_SUCCESS)).build());
}
@Test
public void addSpeedListener_returnsSpeedWithUnknownValuesIfNoResponses()
throws InterruptedException {
// Create "PERF_VEHICLE_SPEED", "PERF_VEHICLE_SPEED_DISPLAY" and "SPEED_DISPLAY_UNIT_ID"
// property IDs list with car zones.
Map<Integer, List<CarZone>> propertyIdsWithCarZones =
ImmutableMap.<Integer, List<CarZone>>builder().put(PERF_VEHICLE_SPEED,
mCarZones).put(PERF_VEHICLE_SPEED_DISPLAY, mCarZones)
.put(SPEED_DISPLAY_UNIT_ID, mCarZones)
.buildKeepingLast();
AtomicReference<Speed> loadedResult = new AtomicReference<>();
OnCarDataAvailableListener<Speed> listener = (data) -> {
loadedResult.set(data);
mCountDownLatch.countDown();
};
mAutomotiveCarInfo.addSpeedListener(mExecutor, listener);
ArgumentCaptor<OnCarPropertyResponseListener> captor = ArgumentCaptor.forClass(
OnCarPropertyResponseListener.class);
verify(mPropertyManager).submitRegisterListenerRequest(eq(propertyIdsWithCarZones),
eq(DEFAULT_SAMPLE_RATE), captor.capture(), eq(mExecutor));
captor.getValue().onCarPropertyResponses(mResponse);
mCountDownLatch.await();
assertThat(loadedResult.get()).isEqualTo(new Speed.Builder().build());
}
@Test
public void getEnergyLevel_verifyResponse() throws InterruptedException {
// Add "INFO_EV_BATTERY_CAPACITY" and "INFO_FUEL_CAPACITY" to the request.
mGetPropertyRequests.add(GetPropertyRequest.create(INFO_EV_BATTERY_CAPACITY));
mGetPropertyRequests.add(GetPropertyRequest.create(INFO_FUEL_CAPACITY));
ArgumentCaptor<OnCarPropertyResponseListener> captor = ArgumentCaptor.forClass(
OnCarPropertyResponseListener.class);
int meterDistanceUnit = 0x21;
int meterVolumeUnit = 0x40;
float evBatteryCapacity = 100f;
float evBatteryLevelValue = 50f;
float fuelCapacity = 120f;
float fuelLevelValue = 50f;
List<CarPropertyResponse<?>> capacities = new ArrayList<>();
capacities.add(CarPropertyResponse.builder().setPropertyId(
INFO_EV_BATTERY_CAPACITY).setStatus(STATUS_SUCCESS).setValue(
evBatteryCapacity).setTimestampMillis(1L).build());
capacities.add(CarPropertyResponse.builder().setPropertyId(INFO_FUEL_CAPACITY).setStatus(
STATUS_SUCCESS).setValue(fuelCapacity).setTimestampMillis(1L).build());
ListenableFuture<List<CarPropertyResponse<?>>> future = Futures.immediateFuture(capacities);
when(mPropertyManager.submitGetPropertyRequest(eq(mGetPropertyRequests), any())).thenReturn(
future);
AtomicReference<EnergyLevel> loadedResult = new AtomicReference<>();
OnCarDataAvailableListener<EnergyLevel> listener = (data) -> {
loadedResult.set(data);
mCountDownLatch.countDown();
};
mAutomotiveCarInfo.addEnergyLevelListener(mExecutor, listener);
// Create "EV_BATTERY_LEVEL", "FUEL_LEVEL", "FUEL_LEVEL_LOW", "RANGE_REMAINING",
// "DISTANCE_DISPLAY_UNITS" and "FUEL_VOLUME_DISPLAY_UNITS" property IDs list with car
// zones.
Map<Integer, List<CarZone>> propertyIdsWithCarZones =
ImmutableMap.<Integer, List<CarZone>>builder()
.put(EV_BATTERY_LEVEL, mCarZones)
.put(FUEL_LEVEL, mCarZones)
.put(FUEL_LEVEL_LOW, mCarZones)
.put(RANGE_REMAINING, mCarZones)
.put(DISTANCE_DISPLAY_UNITS, mCarZones)
.put(FUEL_VOLUME_DISPLAY_UNITS, mCarZones)
.buildKeepingLast();
verify(mPropertyManager, times(1)).submitGetPropertyRequest(eq(mGetPropertyRequests),
eq(mExecutor));
verify(mPropertyManager, times(1)).submitRegisterListenerRequest(
eq(propertyIdsWithCarZones), eq(DEFAULT_SAMPLE_RATE), captor.capture(),
eq(mExecutor));
mResponse.add(CarPropertyResponse.builder().setPropertyId(EV_BATTERY_LEVEL).setStatus(
STATUS_SUCCESS).setValue(evBatteryLevelValue).setTimestampMillis(1L).build());
mResponse.add(CarPropertyResponse.builder().setPropertyId(FUEL_LEVEL).setStatus(
STATUS_SUCCESS).setValue(fuelLevelValue).setTimestampMillis(1L).build());
mResponse.add(CarPropertyResponse.builder().setPropertyId(FUEL_LEVEL_LOW).setStatus(
STATUS_SUCCESS).setValue(true).setTimestampMillis(1L).build());
mResponse.add(CarPropertyResponse.builder().setPropertyId(RANGE_REMAINING).setStatus(
STATUS_SUCCESS).setValue(5f).setTimestampMillis(1L).build());
mResponse.add(CarPropertyResponse.builder().setPropertyId(DISTANCE_DISPLAY_UNITS).setStatus(
STATUS_SUCCESS).setValue(meterDistanceUnit).setTimestampMillis(1L).build());
mResponse.add(CarPropertyResponse.builder().setPropertyId(
FUEL_VOLUME_DISPLAY_UNITS).setStatus(STATUS_SUCCESS).setValue(
meterVolumeUnit).setTimestampMillis(1L).build());
captor.getValue().onCarPropertyResponses(mResponse);
mCountDownLatch.await();
EnergyLevel energyLevel = loadedResult.get();
assertThat(energyLevel.getBatteryPercent().getValue()).isEqualTo(
evBatteryLevelValue / evBatteryCapacity * 100);
assertThat(energyLevel.getFuelPercent().getValue()).isEqualTo(
fuelLevelValue / fuelCapacity * 100);
assertThat(energyLevel.getEnergyIsLow().getValue()).isEqualTo(true);
assertThat(energyLevel.getRangeRemainingMeters().getValue()).isEqualTo(5f);
assertThat(energyLevel.getDistanceDisplayUnit().getValue()).isEqualTo(2);
assertThat(energyLevel.getFuelVolumeDisplayUnit().getValue()).isEqualTo(201);
}
@Test
public void addEnergyLevelListener_handlesReponsesWithDifferentStatuses()
throws InterruptedException {
// Add "INFO_EV_BATTERY_CAPACITY" and "INFO_FUEL_CAPACITY" to the request.
mGetPropertyRequests.add(GetPropertyRequest.create(INFO_EV_BATTERY_CAPACITY));
mGetPropertyRequests.add(GetPropertyRequest.create(INFO_FUEL_CAPACITY));
ArgumentCaptor<OnCarPropertyResponseListener> captor = ArgumentCaptor.forClass(
OnCarPropertyResponseListener.class);
int meterDistanceUnit = 0x21;
float evBatteryCapacity = 100f;
float fuelCapacity = 120f;
List<CarPropertyResponse<?>> capacities = new ArrayList<>();
capacities.add(CarPropertyResponse.builder().setPropertyId(
INFO_EV_BATTERY_CAPACITY).setStatus(STATUS_SUCCESS).setValue(
evBatteryCapacity).setTimestampMillis(1L).build());
capacities.add(CarPropertyResponse.builder().setPropertyId(INFO_FUEL_CAPACITY).setStatus(
STATUS_SUCCESS).setValue(fuelCapacity).setTimestampMillis(1L).build());
ListenableFuture<List<CarPropertyResponse<?>>> future = Futures.immediateFuture(capacities);
when(mPropertyManager.submitGetPropertyRequest(eq(mGetPropertyRequests), any())).thenReturn(
future);
AtomicReference<EnergyLevel> loadedResult = new AtomicReference<>();
OnCarDataAvailableListener<EnergyLevel> listener = (data) -> {
loadedResult.set(data);
mCountDownLatch.countDown();
};
mAutomotiveCarInfo.addEnergyLevelListener(mExecutor, listener);
// Create "EV_BATTERY_LEVEL", "FUEL_LEVEL", "FUEL_LEVEL_LOW", "RANGE_REMAINING",
// "DISTANCE_DISPLAY_UNITS" and "FUEL_VOLUME_DISPLAY_UNITS" property IDs list with car
// zones.
Map<Integer, List<CarZone>> propertyIdsWithCarZones =
ImmutableMap.<Integer, List<CarZone>>builder().put(EV_BATTERY_LEVEL, mCarZones).put(
FUEL_LEVEL, mCarZones).put(FUEL_LEVEL_LOW, mCarZones).put(RANGE_REMAINING,
mCarZones).put(DISTANCE_DISPLAY_UNITS, mCarZones).put(
FUEL_VOLUME_DISPLAY_UNITS, mCarZones).buildKeepingLast();
verify(mPropertyManager, times(1)).submitGetPropertyRequest(eq(mGetPropertyRequests),
eq(mExecutor));
verify(mPropertyManager, times(1)).submitRegisterListenerRequest(
eq(propertyIdsWithCarZones), eq(DEFAULT_SAMPLE_RATE), captor.capture(),
eq(mExecutor));
mResponse.add(CarPropertyResponse.builder().setPropertyId(EV_BATTERY_LEVEL).setStatus(
STATUS_UNKNOWN).setTimestampMillis(1L).build());
mResponse.add(CarPropertyResponse.builder().setPropertyId(FUEL_LEVEL).setStatus(
STATUS_UNAVAILABLE).setTimestampMillis(2L).build());
mResponse.add(CarPropertyResponse.builder().setPropertyId(FUEL_LEVEL_LOW).setStatus(
STATUS_SUCCESS).setValue(true).setTimestampMillis(3L).build());
mResponse.add(CarPropertyResponse.builder().setPropertyId(RANGE_REMAINING).setStatus(
STATUS_UNKNOWN).setTimestampMillis(1L).build());
mResponse.add(CarPropertyResponse.builder().setPropertyId(DISTANCE_DISPLAY_UNITS).setStatus(
STATUS_SUCCESS).setValue(meterDistanceUnit).setTimestampMillis(4L).build());
mResponse.add(CarPropertyResponse.builder().setPropertyId(
FUEL_VOLUME_DISPLAY_UNITS).setStatus(STATUS_UNAVAILABLE).setTimestampMillis(
6L).build());
captor.getValue().onCarPropertyResponses(mResponse);
mCountDownLatch.await();
assertThat(loadedResult.get()).isEqualTo(new EnergyLevel.Builder().setBatteryPercent(
new CarValue<>(null, 1, STATUS_UNKNOWN)).setFuelPercent(
new CarValue<>(null, 2, STATUS_UNAVAILABLE)).setEnergyIsLow(
new CarValue<>(true, 3, STATUS_SUCCESS)).setRangeRemainingMeters(
new CarValue<>(null, 1, STATUS_UNKNOWN)).setFuelVolumeDisplayUnit(
new CarValue<>(null, 6, STATUS_UNAVAILABLE)).setDistanceDisplayUnit(
new CarValue<>(CarUnit.METER, 4, STATUS_SUCCESS)).build());
}
@Test
public void addEnergyLevelListener_returnsEnergyLevelWithUnknownValuesIfNoResponses()
throws InterruptedException {
// Add "INFO_EV_BATTERY_CAPACITY" and "INFO_FUEL_CAPACITY" to the request.
mGetPropertyRequests.add(GetPropertyRequest.create(INFO_EV_BATTERY_CAPACITY));
mGetPropertyRequests.add(GetPropertyRequest.create(INFO_FUEL_CAPACITY));
List<CarPropertyResponse<?>> capacities = new ArrayList<>();
when(mPropertyManager.submitGetPropertyRequest(eq(mGetPropertyRequests), any())).thenReturn(
Futures.immediateFuture(capacities));
AtomicReference<EnergyLevel> loadedResult = new AtomicReference<>();
OnCarDataAvailableListener<EnergyLevel> listener = (data) -> {
loadedResult.set(data);
mCountDownLatch.countDown();
};
mAutomotiveCarInfo.addEnergyLevelListener(mExecutor, listener);
verify(mPropertyManager, times(1)).submitGetPropertyRequest(
eq(mGetPropertyRequests), eq(mExecutor));
// Create "EV_BATTERY_LEVEL", "FUEL_LEVEL", "FUEL_LEVEL_LOW", "RANGE_REMAINING",
// "DISTANCE_DISPLAY_UNITS" and "FUEL_VOLUME_DISPLAY_UNITS" property IDs list with car
// zones.
Map<Integer, List<CarZone>> propertyIdsWithCarZones =
ImmutableMap.<Integer, List<CarZone>>builder()
.put(EV_BATTERY_LEVEL, mCarZones)
.put(FUEL_LEVEL, mCarZones)
.put(FUEL_LEVEL_LOW, mCarZones)
.put(RANGE_REMAINING, mCarZones)
.put(DISTANCE_DISPLAY_UNITS, mCarZones)
.put(FUEL_VOLUME_DISPLAY_UNITS, mCarZones)
.buildKeepingLast();
ArgumentCaptor<OnCarPropertyResponseListener> captor = ArgumentCaptor.forClass(
OnCarPropertyResponseListener.class);
verify(mPropertyManager, times(1)).submitRegisterListenerRequest(
eq(propertyIdsWithCarZones), eq(DEFAULT_SAMPLE_RATE), captor.capture(),
eq(mExecutor));
captor.getValue().onCarPropertyResponses(mResponse);
mCountDownLatch.await();
assertThat(loadedResult.get()).isEqualTo(new EnergyLevel.Builder().build());
}
@Test
public void getEnergyLevel_withUnavailableCapacityValues() throws InterruptedException {
// Add "INFO_EV_BATTERY_CAPACITY" and "INFO_FUEL_CAPACITY" to the request.
mGetPropertyRequests.add(GetPropertyRequest.create(INFO_EV_BATTERY_CAPACITY));
mGetPropertyRequests.add(GetPropertyRequest.create(INFO_FUEL_CAPACITY));
ArgumentCaptor<OnCarPropertyResponseListener> captor = ArgumentCaptor.forClass(
OnCarPropertyResponseListener.class);
int meterDistanceUnit = 0x21;
int meterVolumeUnit = 0x40;
float evBatteryLevelValue = 50f;
float fuelLevelValue = 50f;
List<CarPropertyResponse<?>> capacities = new ArrayList<>();
capacities.add(CarPropertyResponse.builder().setPropertyId(
INFO_EV_BATTERY_CAPACITY).setStatus(STATUS_UNAVAILABLE).setTimestampMillis(
1L).build());
capacities.add(CarPropertyResponse.builder().setPropertyId(INFO_FUEL_CAPACITY).setStatus(
STATUS_UNAVAILABLE).setTimestampMillis(1L).build());
ListenableFuture<List<CarPropertyResponse<?>>> future = Futures.immediateFuture(capacities);
when(mPropertyManager.submitGetPropertyRequest(eq(mGetPropertyRequests), any())).thenReturn(
future);
AtomicReference<EnergyLevel> loadedResult = new AtomicReference<>();
OnCarDataAvailableListener<EnergyLevel> listener = (data) -> {
loadedResult.set(data);
mCountDownLatch.countDown();
};
mAutomotiveCarInfo.addEnergyLevelListener(mExecutor, listener);
// Create "EV_BATTERY_LEVEL", "FUEL_LEVEL", "FUEL_LEVEL_LOW", "RANGE_REMAINING",
// "DISTANCE_DISPLAY_UNITS" and "FUEL_VOLUME_DISPLAY_UNITS" property IDs list with car
// zones.
Map<Integer, List<CarZone>> propertyIdsWithCarZones =
ImmutableMap.<Integer, List<CarZone>>builder()
.put(EV_BATTERY_LEVEL, mCarZones)
.put(FUEL_LEVEL, mCarZones)
.put(FUEL_LEVEL_LOW, mCarZones)
.put(RANGE_REMAINING, mCarZones)
.put(DISTANCE_DISPLAY_UNITS, mCarZones)
.put(FUEL_VOLUME_DISPLAY_UNITS, mCarZones)
.buildKeepingLast();
verify(mPropertyManager, times(1)).submitGetPropertyRequest(
eq(mGetPropertyRequests),
eq(mExecutor));
verify(mPropertyManager, times(1)).submitRegisterListenerRequest(
eq(propertyIdsWithCarZones), eq(DEFAULT_SAMPLE_RATE), captor.capture(),
eq(mExecutor));
mResponse.add(CarPropertyResponse.builder().setPropertyId(EV_BATTERY_LEVEL).setStatus(
STATUS_SUCCESS).setValue(evBatteryLevelValue).setTimestampMillis(1L).build());
mResponse.add(CarPropertyResponse.builder().setPropertyId(FUEL_LEVEL).setStatus(
STATUS_SUCCESS).setValue(fuelLevelValue).setTimestampMillis(1L).build());
mResponse.add(CarPropertyResponse.builder().setPropertyId(FUEL_LEVEL_LOW).setStatus(
STATUS_SUCCESS).setValue(true).setTimestampMillis(1L).build());
mResponse.add(CarPropertyResponse.builder().setPropertyId(RANGE_REMAINING).setStatus(
STATUS_SUCCESS).setValue(5f).setTimestampMillis(1L).build());
mResponse.add(CarPropertyResponse.builder().setPropertyId(DISTANCE_DISPLAY_UNITS).setStatus(
STATUS_SUCCESS).setValue(meterDistanceUnit).setTimestampMillis(1L).build());
mResponse.add(CarPropertyResponse.builder().setPropertyId(
FUEL_VOLUME_DISPLAY_UNITS).setStatus(STATUS_SUCCESS).setValue(
meterVolumeUnit).setTimestampMillis(1L).build());
captor.getValue().onCarPropertyResponses(mResponse);
mCountDownLatch.await();
EnergyLevel energyLevel = loadedResult.get();
// Battery percent and fuel percent should be UNKNOWN_FLOAT since we can not get
// the capacity of battery and fuel property.
assertThat(energyLevel.getBatteryPercent().getValue()).isEqualTo(
CarValue.UNKNOWN_FLOAT.getValue());
assertThat(energyLevel.getFuelPercent().getValue()).isEqualTo(
CarValue.UNKNOWN_FLOAT.getValue());
// The other properties should still work without capacity values
assertThat(energyLevel.getEnergyIsLow().getValue()).isEqualTo(true);
assertThat(energyLevel.getRangeRemainingMeters().getValue()).isEqualTo(5f);
assertThat(energyLevel.getDistanceDisplayUnit().getValue()).isEqualTo(2);
assertThat(energyLevel.getFuelVolumeDisplayUnit().getValue()).isEqualTo(201);
}
@Test
public void getEnergyLevel_SuccessfulPartialResponses() throws InterruptedException {
// Add "INFO_EV_BATTERY_CAPACITY" and "INFO_FUEL_CAPACITY" to the request.
mGetPropertyRequests.add(GetPropertyRequest.create(INFO_EV_BATTERY_CAPACITY));
mGetPropertyRequests.add(GetPropertyRequest.create(INFO_FUEL_CAPACITY));
ArgumentCaptor<OnCarPropertyResponseListener> captor = ArgumentCaptor.forClass(
OnCarPropertyResponseListener.class);
int meterDistanceUnit = 0x21;
float evBatteryCapacity = 100f;
float evBatteryLevelValue = 50f;
float fuelCapacity = 120f;
float fuelLevelValue = 50f;
List<CarPropertyResponse<?>> capacities = new ArrayList<>();
capacities.add(CarPropertyResponse.builder().setPropertyId(
INFO_EV_BATTERY_CAPACITY).setStatus(STATUS_SUCCESS).setValue(
evBatteryCapacity).setTimestampMillis(1L).build());
capacities.add(CarPropertyResponse.builder().setPropertyId(INFO_FUEL_CAPACITY).setStatus(
STATUS_SUCCESS).setValue(fuelCapacity).setTimestampMillis(1L).build());
ListenableFuture<List<CarPropertyResponse<?>>> future = Futures.immediateFuture(capacities);
when(mPropertyManager.submitGetPropertyRequest(eq(mGetPropertyRequests), any())).thenReturn(
future);
AtomicReference<EnergyLevel> loadedResult = new AtomicReference<>();
OnCarDataAvailableListener<EnergyLevel> listener = (data) -> {
loadedResult.set(data);
mCountDownLatch.countDown();
};
mAutomotiveCarInfo.addEnergyLevelListener(mExecutor, listener);
// Create "EV_BATTERY_LEVEL", "FUEL_LEVEL", "FUEL_LEVEL_LOW", "RANGE_REMAINING",
// "DISTANCE_DISPLAY_UNITS" and "FUEL_VOLUME_DISPLAY_UNITS" property IDs list with car
// zones.
Map<Integer, List<CarZone>> propertyIdsWithCarZones =
ImmutableMap.<Integer, List<CarZone>>builder()
.put(EV_BATTERY_LEVEL, mCarZones)
.put(FUEL_LEVEL, mCarZones)
.put(FUEL_LEVEL_LOW, mCarZones)
.put(RANGE_REMAINING, mCarZones)
.put(DISTANCE_DISPLAY_UNITS, mCarZones)
.put(FUEL_VOLUME_DISPLAY_UNITS, mCarZones)
.buildKeepingLast();
verify(mPropertyManager, times(1)).submitGetPropertyRequest(eq(mGetPropertyRequests),
eq(mExecutor));
verify(mPropertyManager, times(1)).submitRegisterListenerRequest(
eq(propertyIdsWithCarZones), eq(DEFAULT_SAMPLE_RATE), captor.capture(),
eq(mExecutor));
// Missing response for FUEL_VOLUME_DISPLAY_UNITS.
mResponse.add(CarPropertyResponse.builder().setPropertyId(EV_BATTERY_LEVEL).setStatus(
STATUS_SUCCESS).setValue(evBatteryLevelValue).setTimestampMillis(1L).build());
mResponse.add(CarPropertyResponse.builder().setPropertyId(FUEL_LEVEL).setStatus(
STATUS_SUCCESS).setValue(fuelLevelValue).setTimestampMillis(1L).build());
mResponse.add(CarPropertyResponse.builder().setPropertyId(FUEL_LEVEL_LOW).setStatus(
STATUS_SUCCESS).setValue(true).setTimestampMillis(1L).build());
mResponse.add(CarPropertyResponse.builder().setPropertyId(RANGE_REMAINING).setStatus(
STATUS_SUCCESS).setValue(5f).setTimestampMillis(1L).build());
mResponse.add(CarPropertyResponse.builder().setPropertyId(DISTANCE_DISPLAY_UNITS).setStatus(
STATUS_SUCCESS).setValue(meterDistanceUnit).setTimestampMillis(1L).build());
captor.getValue().onCarPropertyResponses(mResponse);
mCountDownLatch.await();
// The partial responses will be returned successfully.
EnergyLevel energyLevel = loadedResult.get();
assertThat(energyLevel.getBatteryPercent().getValue()).isEqualTo(
evBatteryLevelValue / evBatteryCapacity * 100);
assertThat(energyLevel.getFuelPercent().getValue()).isEqualTo(
fuelLevelValue / fuelCapacity * 100);
assertThat(energyLevel.getEnergyIsLow().getValue()).isEqualTo(true);
assertThat(energyLevel.getRangeRemainingMeters().getValue()).isEqualTo(5f);
assertThat(energyLevel.getDistanceDisplayUnit().getValue()).isEqualTo(2);
}
}