blob: 3482327a6c894021284c56cad9a930df04095fae [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 android.car.cts;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.testng.Assert.assertThrows;
import android.car.Car;
import android.car.VehicleAreaSeat;
import android.car.VehicleAreaType;
import android.car.VehiclePropertyIds;
import android.car.hardware.CarPropertyConfig;
import android.car.hardware.CarPropertyValue;
import android.car.hardware.property.CarPropertyManager;
import android.car.hardware.property.CarPropertyManager.CarPropertyEventCallback;
import android.platform.test.annotations.RequiresDevice;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.ArraySet;
import android.util.SparseArray;
import androidx.annotation.GuardedBy;
import androidx.test.runner.AndroidJUnit4;
import com.android.compatibility.common.util.CddTest;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@SmallTest
@RequiresDevice
@RunWith(AndroidJUnit4.class)
public class CarPropertyManagerTest extends CarApiTestBase {
private static final String TAG = CarPropertyManagerTest.class.getSimpleName();
private static final long WAIT_CALLBACK = 1500L;
private static final int NO_EVENTS = 0;
private static final int ONCHANGE_RATE_EVENT_COUNTER = 1;
private static final int UI_RATE_EVENT_COUNTER = 5;
private static final int FAST_OR_FASTEST_EVENT_COUNTER = 10;
private CarPropertyManager mCarPropertyManager;
/** contains property Ids for the properties required by CDD*/
private ArraySet<Integer> mPropertyIds = new ArraySet<>();
private static class CarPropertyEventCounter implements CarPropertyEventCallback {
private final Object mLock = new Object();
private int mCounter = FAST_OR_FASTEST_EVENT_COUNTER;
private CountDownLatch mCountDownLatch = new CountDownLatch(mCounter);
@GuardedBy("mLock")
private SparseArray<Integer> mEventCounter = new SparseArray<>();
@GuardedBy("mLock")
private SparseArray<Integer> mErrorCounter = new SparseArray<>();
public int receivedEvent(int propId) {
int val;
synchronized (mLock) {
val = mEventCounter.get(propId, 0);
}
return val;
}
public int receivedError(int propId) {
int val;
synchronized (mLock) {
val = mErrorCounter.get(propId, 0);
}
return val;
}
@Override
public void onChangeEvent(CarPropertyValue value) {
synchronized (mLock) {
int val = mEventCounter.get(value.getPropertyId(), 0) + 1;
mEventCounter.put(value.getPropertyId(), val);
}
mCountDownLatch.countDown();
}
@Override
public void onErrorEvent(int propId, int zone) {
synchronized (mLock) {
int val = mErrorCounter.get(propId, 0) + 1;
mErrorCounter.put(propId, val);
}
}
public void resetCountDownLatch(int counter) {
mCountDownLatch = new CountDownLatch(counter);
mCounter = counter;
}
public void assertOnChangeEventCalled() throws InterruptedException {
if (!mCountDownLatch.await(WAIT_CALLBACK, TimeUnit.MILLISECONDS)) {
throw new IllegalStateException("Callback is not called:" + mCounter + "times in "
+ WAIT_CALLBACK + " ms.");
}
}
public void assertOnChangeEventNotCalled() throws InterruptedException {
// Once get an event, fail the test.
mCountDownLatch = new CountDownLatch(1);
if (mCountDownLatch.await(WAIT_CALLBACK, TimeUnit.MILLISECONDS)) {
throw new IllegalStateException("Callback is called in "
+ WAIT_CALLBACK + " ms.");
}
}
}
@Before
public void setUp() throws Exception {
super.setUp();
mCarPropertyManager = (CarPropertyManager) getCar().getCarManager(Car.PROPERTY_SERVICE);
mPropertyIds.add(VehiclePropertyIds.PERF_VEHICLE_SPEED);
mPropertyIds.add(VehiclePropertyIds.GEAR_SELECTION);
mPropertyIds.add(VehiclePropertyIds.NIGHT_MODE);
mPropertyIds.add(VehiclePropertyIds.PARKING_BRAKE_ON);
}
/**
* Test for {@link CarPropertyManager#getPropertyList()}
*/
@Test
public void testGetPropertyList() {
List<CarPropertyConfig> allConfigs = mCarPropertyManager.getPropertyList();
assertThat(allConfigs).isNotNull();
}
/**
* Test for {@link CarPropertyManager#getPropertyList(ArraySet)}
*/
@Test
public void testGetPropertyListWithArraySet() {
List<CarPropertyConfig> requiredConfigs = mCarPropertyManager.getPropertyList(mPropertyIds);
// Vehicles need to implement all of those properties
assertThat(requiredConfigs.size()).isEqualTo(mPropertyIds.size());
}
/**
* Test for {@link CarPropertyManager#getCarPropertyConfig(int)}
*/
@Test
public void testGetPropertyConfig() {
List<CarPropertyConfig> allConfigs = mCarPropertyManager.getPropertyList();
for (CarPropertyConfig cfg : allConfigs) {
assertThat(mCarPropertyManager.getCarPropertyConfig(cfg.getPropertyId())).isNotNull();
}
}
/**
* Test for {@link CarPropertyManager#getAreaId(int, int)}
*/
@Test
public void testGetAreaId() {
// For global properties, getAreaId should always return 0.
List<CarPropertyConfig> allConfigs = mCarPropertyManager.getPropertyList();
for (CarPropertyConfig cfg : allConfigs) {
if (cfg.isGlobalProperty()) {
assertThat(mCarPropertyManager.getAreaId(cfg.getPropertyId(),
VehicleAreaSeat.SEAT_ROW_1_LEFT))
.isEqualTo(VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL);
} else {
int[] areaIds = cfg.getAreaIds();
// Because areaId in propConfig must not be overlapped with each other.
// The result should be itself.
for (int areaIdInConfig : areaIds) {
int areaIdByCarPropertyManager =
mCarPropertyManager.getAreaId(cfg.getPropertyId(), areaIdInConfig);
assertThat(areaIdByCarPropertyManager).isEqualTo(areaIdInConfig);
}
}
}
}
@CddTest(requirement="2.5.1")
@Test
public void testMustSupportGearSelection() throws Exception {
assertWithMessage("Must support GEAR_SELECTION")
.that(mCarPropertyManager.getCarPropertyConfig(VehiclePropertyIds.GEAR_SELECTION))
.isNotNull();
}
@CddTest(requirement="2.5.1")
@Test
public void testMustSupportNightMode() {
assertWithMessage("Must support NIGHT_MODE")
.that(mCarPropertyManager.getCarPropertyConfig(VehiclePropertyIds.NIGHT_MODE))
.isNotNull();
}
@CddTest(requirement="2.5.1")
@Test
public void testMustSupportPerfVehicleSpeed() throws Exception {
assertWithMessage("Must support PERF_VEHICLE_SPEED")
.that(mCarPropertyManager.getCarPropertyConfig(
VehiclePropertyIds.PERF_VEHICLE_SPEED)).isNotNull();
}
@CddTest(requirement = "2.5.1")
@Test
public void testMustSupportParkingBrakeOn() throws Exception {
assertWithMessage("Must support PARKING_BRAKE_ON")
.that(mCarPropertyManager.getCarPropertyConfig(VehiclePropertyIds.PARKING_BRAKE_ON))
.isNotNull();
}
@SuppressWarnings("unchecked")
@Test
public void testGetProperty() {
List<CarPropertyConfig> configs = mCarPropertyManager.getPropertyList(mPropertyIds);
for (CarPropertyConfig cfg : configs) {
if (cfg.getAccess() == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ) {
int[] areaIds = getAreaIdsHelper(cfg);
int propId = cfg.getPropertyId();
// no guarantee if we can get values, just call and check if it throws exception.
if (cfg.getPropertyType() == Boolean.class) {
for (int areaId : areaIds) {
mCarPropertyManager.getBooleanProperty(propId, areaId);
}
} else if (cfg.getPropertyType() == Integer.class) {
for (int areaId : areaIds) {
mCarPropertyManager.getIntProperty(propId, areaId);
}
} else if (cfg.getPropertyType() == Float.class) {
for (int areaId : areaIds) {
mCarPropertyManager.getFloatProperty(propId, areaId);
}
} else if (cfg.getPropertyType() == Integer[].class) {
for (int areId : areaIds) {
mCarPropertyManager.getIntArrayProperty(propId, areId);
}
} else {
for (int areaId : areaIds) {
mCarPropertyManager.getProperty(
cfg.getPropertyType(), propId, areaId);;
}
}
}
}
}
@Test
public void testGetIntArrayProperty() {
List<CarPropertyConfig> allConfigs = mCarPropertyManager.getPropertyList();
for (CarPropertyConfig cfg : allConfigs) {
if (cfg.getAccess() == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_NONE
|| cfg.getAccess() == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE
|| cfg.getPropertyType() != Integer[].class) {
// skip the test if the property is not readable or not an int array type property.
continue;
}
switch (cfg.getPropertyId()) {
case VehiclePropertyIds.INFO_FUEL_TYPE:
int[] fuelTypes = mCarPropertyManager.getIntArrayProperty(cfg.getPropertyId(),
VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL);
verifyEnumsRange(EXPECTED_FUEL_TYPES, fuelTypes);
break;
case VehiclePropertyIds.INFO_MULTI_EV_PORT_LOCATIONS:
int[] evPortLocations = mCarPropertyManager.getIntArrayProperty(
cfg.getPropertyId(),VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL);
verifyEnumsRange(EXPECTED_PORT_LOCATIONS, evPortLocations);
break;
default:
int[] areaIds = getAreaIdsHelper(cfg);
for(int areaId : areaIds) {
mCarPropertyManager.getIntArrayProperty(cfg.getPropertyId(), areaId);
}
}
}
}
private void verifyEnumsRange(List<Integer> expectedResults, int[] results) {
assertThat(results).isNotNull();
// If the property is not implemented in cars, getIntArrayProperty returns an empty array.
if (results.length == 0) {
return;
}
for (int result : results) {
assertThat(result).isIn(expectedResults);
}
}
@Test
public void testIsPropertyAvailable() {
List<CarPropertyConfig> configs = mCarPropertyManager.getPropertyList(mPropertyIds);
for (CarPropertyConfig cfg : configs) {
int[] areaIds = getAreaIdsHelper(cfg);
for (int areaId : areaIds) {
assertThat(mCarPropertyManager.isPropertyAvailable(cfg.getPropertyId(), areaId))
.isTrue();
}
}
}
@Test
public void testSetProperty() {
List<CarPropertyConfig> configs = mCarPropertyManager.getPropertyList();
for (CarPropertyConfig cfg : configs) {
if (cfg.getAccess() == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE
&& cfg.getPropertyType() == Boolean.class) {
// In R, there is no property which is writable for third-party apps.
for (int areaId : getAreaIdsHelper(cfg)) {
assertThrows(SecurityException.class,
() -> mCarPropertyManager.setBooleanProperty(
cfg.getPropertyId(), areaId,true));
}
}
}
}
@Test
public void testRegisterCallback() throws Exception {
//Test on registering a invalid property
int invalidPropertyId = -1;
boolean isRegistered = mCarPropertyManager.registerCallback(
new CarPropertyEventCounter(), invalidPropertyId, 0);
assertThat(isRegistered).isFalse();
// Test for continuous properties
int vehicleSpeed = VehiclePropertyIds.PERF_VEHICLE_SPEED;
CarPropertyEventCounter speedListenerUI = new CarPropertyEventCounter();
CarPropertyEventCounter speedListenerFast = new CarPropertyEventCounter();
assertThat(speedListenerUI.receivedEvent(vehicleSpeed)).isEqualTo(NO_EVENTS);
assertThat(speedListenerUI.receivedError(vehicleSpeed)).isEqualTo(NO_EVENTS);
assertThat(speedListenerFast.receivedEvent(vehicleSpeed)).isEqualTo(NO_EVENTS);
assertThat(speedListenerFast.receivedError(vehicleSpeed)).isEqualTo(NO_EVENTS);
mCarPropertyManager.registerCallback(speedListenerUI, vehicleSpeed,
CarPropertyManager.SENSOR_RATE_UI);
mCarPropertyManager.registerCallback(speedListenerFast, vehicleSpeed,
CarPropertyManager.SENSOR_RATE_FASTEST);
speedListenerUI.resetCountDownLatch(UI_RATE_EVENT_COUNTER);
speedListenerUI.assertOnChangeEventCalled();
assertThat(speedListenerUI.receivedEvent(vehicleSpeed)).isGreaterThan(NO_EVENTS);
assertThat(speedListenerFast.receivedEvent(vehicleSpeed)).isGreaterThan(
speedListenerUI.receivedEvent(vehicleSpeed));
mCarPropertyManager.unregisterCallback(speedListenerFast);
mCarPropertyManager.unregisterCallback(speedListenerUI);
// Test for on_change properties
int nightMode = VehiclePropertyIds.NIGHT_MODE;
CarPropertyEventCounter nightModeListener = new CarPropertyEventCounter();
nightModeListener.resetCountDownLatch(ONCHANGE_RATE_EVENT_COUNTER);
mCarPropertyManager.registerCallback(nightModeListener, nightMode, 0);
nightModeListener.assertOnChangeEventCalled();
assertThat(nightModeListener.receivedEvent(nightMode)).isEqualTo(1);
mCarPropertyManager.unregisterCallback(nightModeListener);
}
@Test
public void testUnregisterCallback() throws Exception {
int vehicleSpeed = VehiclePropertyIds.PERF_VEHICLE_SPEED;
CarPropertyEventCounter speedListenerNormal = new CarPropertyEventCounter();
CarPropertyEventCounter speedListenerUI = new CarPropertyEventCounter();
mCarPropertyManager.registerCallback(speedListenerNormal, vehicleSpeed,
CarPropertyManager.SENSOR_RATE_NORMAL);
// test on unregistering a callback that was never registered
try {
mCarPropertyManager.unregisterCallback(speedListenerUI);
} catch (Exception e) {
Assert.fail();
}
mCarPropertyManager.registerCallback(speedListenerUI, vehicleSpeed,
CarPropertyManager.SENSOR_RATE_UI);
speedListenerUI.resetCountDownLatch(UI_RATE_EVENT_COUNTER);
speedListenerUI.assertOnChangeEventCalled();
mCarPropertyManager.unregisterCallback(speedListenerNormal, vehicleSpeed);
int currentEventNormal = speedListenerNormal.receivedEvent(vehicleSpeed);
int currentEventUI = speedListenerUI.receivedEvent(vehicleSpeed);
speedListenerNormal.assertOnChangeEventNotCalled();
assertThat(speedListenerNormal.receivedEvent(vehicleSpeed)).isEqualTo(currentEventNormal);
assertThat(speedListenerUI.receivedEvent(vehicleSpeed)).isNotEqualTo(currentEventUI);
mCarPropertyManager.unregisterCallback(speedListenerUI);
speedListenerUI.assertOnChangeEventNotCalled();
currentEventUI = speedListenerUI.receivedEvent(vehicleSpeed);
assertThat(speedListenerUI.receivedEvent(vehicleSpeed)).isEqualTo(currentEventUI);
}
@Test
public void testUnregisterWithPropertyId() throws Exception {
// Ignores the test if wheel_tick property does not exist in the car.
Assume.assumeTrue("WheelTick is not available, skip unregisterCallback test",
mCarPropertyManager.isPropertyAvailable(
VehiclePropertyIds.WHEEL_TICK, VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL));
CarPropertyEventCounter speedAndWheelTicksListener = new CarPropertyEventCounter();
mCarPropertyManager.registerCallback(speedAndWheelTicksListener,
VehiclePropertyIds.PERF_VEHICLE_SPEED, CarPropertyManager.SENSOR_RATE_FASTEST);
mCarPropertyManager.registerCallback(speedAndWheelTicksListener,
VehiclePropertyIds.WHEEL_TICK, CarPropertyManager.SENSOR_RATE_FASTEST);
speedAndWheelTicksListener.resetCountDownLatch(FAST_OR_FASTEST_EVENT_COUNTER);
speedAndWheelTicksListener.assertOnChangeEventCalled();
mCarPropertyManager.unregisterCallback(speedAndWheelTicksListener,
VehiclePropertyIds.PERF_VEHICLE_SPEED);
speedAndWheelTicksListener.resetCountDownLatch(FAST_OR_FASTEST_EVENT_COUNTER);
speedAndWheelTicksListener.assertOnChangeEventCalled();
int currentSpeedEvents = speedAndWheelTicksListener.receivedEvent(
VehiclePropertyIds.PERF_VEHICLE_SPEED);
int currentWheelTickEvents = speedAndWheelTicksListener.receivedEvent(
VehiclePropertyIds.WHEEL_TICK);
speedAndWheelTicksListener.resetCountDownLatch(FAST_OR_FASTEST_EVENT_COUNTER);
speedAndWheelTicksListener.assertOnChangeEventCalled();
int speedEventsAfterUnregister = speedAndWheelTicksListener.receivedEvent(
VehiclePropertyIds.PERF_VEHICLE_SPEED);
int wheelTicksEventsAfterUnregister = speedAndWheelTicksListener.receivedEvent(
VehiclePropertyIds.WHEEL_TICK);
assertThat(currentSpeedEvents).isEqualTo(speedEventsAfterUnregister);
assertThat(wheelTicksEventsAfterUnregister).isGreaterThan(currentWheelTickEvents);
}
// Returns {0} if the property is global property, otherwise query areaId for CarPropertyConfig
private int[] getAreaIdsHelper(CarPropertyConfig config) {
if (config.isGlobalProperty()) {
int[] areaIds = {0};
return areaIds;
} else {
return config.getAreaIds();
}
}
}