blob: c0bb570e9d7f1df68262b01cb1106f7698bb071b [file] [log] [blame]
/*
* Copyright (C) 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 android.car.cts.utils;
import static android.car.cts.utils.ShellPermissionUtils.runWithShellPermissionIdentity;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertThrows;
import static org.junit.Assume.assumeNotNull;
import android.car.VehicleAreaDoor;
import android.car.VehicleAreaMirror;
import android.car.VehicleAreaSeat;
import android.car.VehicleAreaType;
import android.car.VehicleAreaWheel;
import android.car.VehicleAreaWindow;
import android.car.VehiclePropertyIds;
import android.car.VehiclePropertyType;
import android.car.hardware.CarPropertyConfig;
import android.car.hardware.CarPropertyValue;
import android.car.hardware.property.CarPropertyManager;
import android.car.hardware.property.CarPropertyManager.GetPropertyCallback;
import android.car.hardware.property.CarPropertyManager.GetPropertyRequest;
import android.car.hardware.property.CarPropertyManager.GetPropertyResult;
import android.car.hardware.property.CarPropertyManager.PropertyAsyncError;
import android.car.hardware.property.PropertyNotAvailableException;
import android.os.SystemClock;
import android.util.SparseArray;
import android.util.SparseIntArray;
import androidx.annotation.Nullable;
import com.android.internal.annotations.GuardedBy;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class VehiclePropertyVerifier<T> {
private static final String CAR_PROPERTY_VALUE_SOURCE_GETTER = "Getter";
private static final String CAR_PROPERTY_VALUE_SOURCE_CALLBACK = "Callback";
private static final float FLOAT_INEQUALITY_THRESHOLD = 0.00001f;
private static final ImmutableSet<Integer> WHEEL_AREAS = ImmutableSet.of(
VehicleAreaWheel.WHEEL_LEFT_FRONT, VehicleAreaWheel.WHEEL_LEFT_REAR,
VehicleAreaWheel.WHEEL_RIGHT_FRONT, VehicleAreaWheel.WHEEL_RIGHT_REAR);
private static final ImmutableSet<Integer> ALL_POSSIBLE_WHEEL_AREA_IDS =
generateAllPossibleAreaIds(WHEEL_AREAS);
private static final ImmutableSet<Integer> WINDOW_AREAS = ImmutableSet.of(
VehicleAreaWindow.WINDOW_FRONT_WINDSHIELD, VehicleAreaWindow.WINDOW_REAR_WINDSHIELD,
VehicleAreaWindow.WINDOW_ROW_1_LEFT, VehicleAreaWindow.WINDOW_ROW_1_RIGHT,
VehicleAreaWindow.WINDOW_ROW_2_LEFT, VehicleAreaWindow.WINDOW_ROW_2_RIGHT,
VehicleAreaWindow.WINDOW_ROW_3_LEFT, VehicleAreaWindow.WINDOW_ROW_3_RIGHT,
VehicleAreaWindow.WINDOW_ROOF_TOP_1, VehicleAreaWindow.WINDOW_ROOF_TOP_2);
private static final ImmutableSet<Integer> ALL_POSSIBLE_WINDOW_AREA_IDS =
generateAllPossibleAreaIds(WINDOW_AREAS);
private static final ImmutableSet<Integer> MIRROR_AREAS = ImmutableSet.of(
VehicleAreaMirror.MIRROR_DRIVER_LEFT, VehicleAreaMirror.MIRROR_DRIVER_RIGHT,
VehicleAreaMirror.MIRROR_DRIVER_CENTER);
private static final ImmutableSet<Integer> ALL_POSSIBLE_MIRROR_AREA_IDS =
generateAllPossibleAreaIds(MIRROR_AREAS);
private static final ImmutableSet<Integer> SEAT_AREAS = ImmutableSet.of(
VehicleAreaSeat.SEAT_ROW_1_LEFT, VehicleAreaSeat.SEAT_ROW_1_CENTER,
VehicleAreaSeat.SEAT_ROW_1_RIGHT, VehicleAreaSeat.SEAT_ROW_2_LEFT,
VehicleAreaSeat.SEAT_ROW_2_CENTER, VehicleAreaSeat.SEAT_ROW_2_RIGHT,
VehicleAreaSeat.SEAT_ROW_3_LEFT, VehicleAreaSeat.SEAT_ROW_3_CENTER,
VehicleAreaSeat.SEAT_ROW_3_RIGHT);
private static final ImmutableSet<Integer> ALL_POSSIBLE_SEAT_AREA_IDS =
generateAllPossibleAreaIds(SEAT_AREAS);
private static final ImmutableSet<Integer> DOOR_AREAS = ImmutableSet.of(
VehicleAreaDoor.DOOR_ROW_1_LEFT, VehicleAreaDoor.DOOR_ROW_1_RIGHT,
VehicleAreaDoor.DOOR_ROW_2_LEFT, VehicleAreaDoor.DOOR_ROW_2_RIGHT,
VehicleAreaDoor.DOOR_ROW_3_LEFT, VehicleAreaDoor.DOOR_ROW_3_RIGHT,
VehicleAreaDoor.DOOR_HOOD, VehicleAreaDoor.DOOR_REAR);
private static final ImmutableSet<Integer> ALL_POSSIBLE_DOOR_AREA_IDS =
generateAllPossibleAreaIds(DOOR_AREAS);
private final int mPropertyId;
private final String mPropertyName;
private final int mAccess;
private final int mAreaType;
private final int mChangeMode;
private final Class<T> mPropertyType;
private final boolean mRequiredProperty;
private final Optional<ConfigArrayVerifier> mConfigArrayVerifier;
private final Optional<CarPropertyValueVerifier> mCarPropertyValueVerifier;
private final Optional<AreaIdsVerifier> mAreaIdsVerifier;
private final Optional<CarPropertyConfigVerifier> mCarPropertyConfigVerifier;
private final ImmutableSet<Integer> mPossibleConfigArrayValues;
private final ImmutableSet<T> mPossibleCarPropertyValues;
private final boolean mRequirePropertyValueToBeInConfigArray;
private final boolean mVerifySetterWithConfigArrayValues;
private final boolean mRequireMinMaxValues;
private final boolean mRequireMinValuesToBeZero;
private final boolean mRequireZeroToBeContainedInMinMaxRanges;
private final boolean mPossiblyDependentOnHvacPowerOn;
private final ImmutableSet<String> mReadPermissions;
private final ImmutableSet<String> mWritePermissions;
private VehiclePropertyVerifier(
int propertyId,
int access,
int areaType,
int changeMode,
Class<T> propertyType,
boolean requiredProperty,
Optional<ConfigArrayVerifier> configArrayVerifier,
Optional<CarPropertyValueVerifier> carPropertyValueVerifier,
Optional<AreaIdsVerifier> areaIdsVerifier,
Optional<CarPropertyConfigVerifier> carPropertyConfigVerifier,
ImmutableSet<Integer> possibleConfigArrayValues,
ImmutableSet<T> possibleCarPropertyValues,
boolean requirePropertyValueToBeInConfigArray,
boolean verifySetterWithConfigArrayValues,
boolean requireMinMaxValues,
boolean requireMinValuesToBeZero,
boolean requireZeroToBeContainedInMinMaxRanges,
boolean possiblyDependentOnHvacPowerOn,
ImmutableSet<String> readPermissions,
ImmutableSet<String> writePermissions) {
mPropertyId = propertyId;
mPropertyName = VehiclePropertyIds.toString(propertyId);
mAccess = access;
mAreaType = areaType;
mChangeMode = changeMode;
mPropertyType = propertyType;
mRequiredProperty = requiredProperty;
mConfigArrayVerifier = configArrayVerifier;
mCarPropertyValueVerifier = carPropertyValueVerifier;
mAreaIdsVerifier = areaIdsVerifier;
mCarPropertyConfigVerifier = carPropertyConfigVerifier;
mPossibleConfigArrayValues = possibleConfigArrayValues;
mPossibleCarPropertyValues = possibleCarPropertyValues;
mRequirePropertyValueToBeInConfigArray = requirePropertyValueToBeInConfigArray;
mVerifySetterWithConfigArrayValues = verifySetterWithConfigArrayValues;
mRequireMinMaxValues = requireMinMaxValues;
mRequireMinValuesToBeZero = requireMinValuesToBeZero;
mRequireZeroToBeContainedInMinMaxRanges = requireZeroToBeContainedInMinMaxRanges;
mPossiblyDependentOnHvacPowerOn = possiblyDependentOnHvacPowerOn;
mReadPermissions = readPermissions;
mWritePermissions = writePermissions;
}
public static <T> Builder<T> newBuilder(
int propertyId, int access, int areaType, int changeMode, Class<T> propertyType) {
return new Builder<>(propertyId, access, areaType, changeMode, propertyType);
}
@Nullable
public static <U> U getDefaultValue(Class<?> clazz) {
if (clazz == Boolean.class) {
return (U) Boolean.TRUE;
}
if (clazz == Integer.class) {
return (U) (Integer) 2;
}
if (clazz == Float.class) {
return (U) (Float) 2.f;
}
if (clazz == Long.class) {
return (U) (Long) 2L;
}
if (clazz == Integer[].class) {
return (U) new Integer[]{2};
}
if (clazz == Float[].class) {
return (U) new Float[]{2.f};
}
if (clazz == Long[].class) {
return (U) new Long[]{2L};
}
if (clazz == String.class) {
return (U) new String("test");
}
if (clazz == byte[].class) {
return (U) new byte[]{(byte) 0xbe, (byte) 0xef};
}
return null;
}
private static String accessToString(int access) {
switch (access) {
case CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_NONE:
return "VEHICLE_PROPERTY_ACCESS_NONE";
case CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ:
return "VEHICLE_PROPERTY_ACCESS_READ";
case CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE:
return "VEHICLE_PROPERTY_ACCESS_WRITE";
case CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE:
return "VEHICLE_PROPERTY_ACCESS_READ_WRITE";
default:
return Integer.toString(access);
}
}
private static String areaTypeToString(int areaType) {
switch (areaType) {
case VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL:
return "VEHICLE_AREA_TYPE_GLOBAL";
case VehicleAreaType.VEHICLE_AREA_TYPE_WINDOW:
return "VEHICLE_AREA_TYPE_WINDOW";
case VehicleAreaType.VEHICLE_AREA_TYPE_DOOR:
return "VEHICLE_AREA_TYPE_DOOR";
case VehicleAreaType.VEHICLE_AREA_TYPE_MIRROR:
return "VEHICLE_AREA_TYPE_MIRROR";
case VehicleAreaType.VEHICLE_AREA_TYPE_SEAT:
return "VEHICLE_AREA_TYPE_SEAT";
case VehicleAreaType.VEHICLE_AREA_TYPE_WHEEL:
return "VEHICLE_AREA_TYPE_WHEEL";
default:
return Integer.toString(areaType);
}
}
private static String changeModeToString(int changeMode) {
switch (changeMode) {
case CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_STATIC:
return "VEHICLE_PROPERTY_CHANGE_MODE_STATIC";
case CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE:
return "VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE";
case CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS:
return "VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS";
default:
return Integer.toString(changeMode);
}
}
public void verify(CarPropertyManager carPropertyManager) {
// This allows updating this variable within a lambda.
AtomicReference<CarPropertyConfig<T>> savedCarPropertyConfig = new AtomicReference<>();
runWithShellPermissionIdentity(
() -> {
CarPropertyConfig<T> carPropertyConfig = (CarPropertyConfig<T>)
carPropertyManager.getCarPropertyConfig(mPropertyId);
if (mRequiredProperty) {
assertWithMessage("Must support " + mPropertyName)
.that(carPropertyConfig)
.isNotNull();
} else {
assumeNotNull(carPropertyConfig);
}
verifyCarPropertyConfig(carPropertyConfig);
savedCarPropertyConfig.set(carPropertyConfig);
CarPropertyConfig<Boolean> hvacPowerOnCarPropertyConfig = null;
SparseArray<Boolean> hvacPowerStateByAreaId = null;
if (mPossiblyDependentOnHvacPowerOn) {
hvacPowerOnCarPropertyConfig = (CarPropertyConfig<Boolean>)
carPropertyManager.getCarPropertyConfig(
VehiclePropertyIds.HVAC_POWER_ON);
if (hvacPowerOnCarPropertyConfig != null && hvacPowerOnCarPropertyConfig
.getConfigArray().contains(mPropertyId)) {
hvacPowerStateByAreaId = getHvacPowerStateByAreaId(
hvacPowerOnCarPropertyConfig, carPropertyManager);
turnOnHvacPower(hvacPowerOnCarPropertyConfig, carPropertyManager);
}
}
verifyCarPropertyValueGetter(carPropertyConfig, carPropertyManager);
verifyCarPropertyValueCallback(carPropertyConfig, carPropertyManager);
verifyCarPropertyValueSetter(carPropertyConfig, carPropertyManager);
verifyGetPropertiesAsync(carPropertyConfig, carPropertyManager);
// TODO(b/266000988): verifySetProeprtiesAsync(...)
if (hvacPowerStateByAreaId != null) {
// TODO(b/265483050): Reenable once the bug is fixed.
// turnOffHvacPower(hvacPowerOnCarPropertyConfig, carPropertyManager);
// verifySetNotAvailable(carPropertyConfig, carPropertyManager);
// restoreHvacPower(hvacPowerOnCarPropertyConfig, carPropertyManager,
// hvacPowerStateByAreaId);
}
},
ImmutableSet.<String>builder()
.addAll(mReadPermissions)
.addAll(mWritePermissions)
.build().toArray(new String[0]));
verifyPermissionNotGrantedException(savedCarPropertyConfig.get(), carPropertyManager);
}
// Get a map from hvac area Ids to hvac power state.
private static SparseArray<Boolean> getHvacPowerStateByAreaId(
CarPropertyConfig<Boolean> hvacPowerOnCarPropertyConfig,
CarPropertyManager carPropertyManager) {
SparseArray<Boolean> powerStateByAreaId = new SparseArray<>();
for (int areaId : hvacPowerOnCarPropertyConfig.getAreaIds()) {
boolean isOn = carPropertyManager.getBooleanProperty(
VehiclePropertyIds.HVAC_POWER_ON, areaId);
powerStateByAreaId.put(areaId, isOn);
}
return powerStateByAreaId;
}
// Turn the power on for all hvac areas.
private static void turnOnHvacPower(
CarPropertyConfig<Boolean> hvacPowerOnCarPropertyConfig,
CarPropertyManager carPropertyManager) {
for (int areaId : hvacPowerOnCarPropertyConfig.getAreaIds()) {
if (carPropertyManager.getBooleanProperty(VehiclePropertyIds.HVAC_POWER_ON, areaId)) {
continue;
}
setPropertyAndWaitForChange(carPropertyManager, VehiclePropertyIds.HVAC_POWER_ON,
Boolean.class, areaId, Boolean.TRUE);
}
}
// Turn the power off for all hvac areas.
private static void turnOffHvacPower(
CarPropertyConfig<Boolean> hvacPowerOnCarPropertyConfig,
CarPropertyManager carPropertyManager) {
for (int areaId : hvacPowerOnCarPropertyConfig.getAreaIds()) {
if (!carPropertyManager.getBooleanProperty(VehiclePropertyIds.HVAC_POWER_ON, areaId)) {
continue;
}
setPropertyAndWaitForChange(carPropertyManager, VehiclePropertyIds.HVAC_POWER_ON,
Boolean.class, areaId, Boolean.FALSE);
}
}
// Restore the hvac power state to the state provided by {@code hvacPowerStateByAreaId}.
private void restoreHvacPower(
CarPropertyConfig<Boolean> hvacPowerOnCarPropertyConfig,
CarPropertyManager carPropertyManager,
SparseArray<Boolean> hvacPowerStateByAreaId) {
for (int i = 0; i < hvacPowerStateByAreaId.size(); i++) {
int areaId = hvacPowerStateByAreaId.keyAt(i);
boolean previousState = hvacPowerStateByAreaId.valueAt(i);
boolean currentState = carPropertyManager.getBooleanProperty(
VehiclePropertyIds.HVAC_POWER_ON, areaId);
if (previousState == currentState) {
continue;
}
setPropertyAndWaitForChange(carPropertyManager, VehiclePropertyIds.HVAC_POWER_ON,
Boolean.class, areaId, previousState);
}
}
private void verifyCarPropertyValueSetter(CarPropertyConfig<T> carPropertyConfig,
CarPropertyManager carPropertyManager) {
if (carPropertyConfig.getAccess() != CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE) {
return;
}
if (Boolean.class.equals(carPropertyConfig.getPropertyType())) {
verifyBooleanPropertySetter(carPropertyConfig, carPropertyManager);
} else if (Integer.class.equals(carPropertyConfig.getPropertyType())) {
verifyIntegerPropertySetter(carPropertyConfig, carPropertyManager);
} else if (Float.class.equals(carPropertyConfig.getPropertyType())) {
verifyFloatPropertySetter(carPropertyConfig, carPropertyManager);
}
}
private void verifyBooleanPropertySetter(CarPropertyConfig<T> carPropertyConfig,
CarPropertyManager carPropertyManager) {
for (int areaId : carPropertyConfig.getAreaIds()) {
CarPropertyValue<Boolean> currentCarPropertyValue = carPropertyManager.getProperty(
mPropertyId, areaId);
verifyCarPropertyValue(carPropertyConfig, currentCarPropertyValue, areaId,
CAR_PROPERTY_VALUE_SOURCE_GETTER);
Boolean valueToSet = !currentCarPropertyValue.getValue();
verifySetProperty(carPropertyConfig, carPropertyManager, areaId, (T) valueToSet);
valueToSet = !valueToSet;
verifySetProperty(carPropertyConfig, carPropertyManager, areaId, (T) valueToSet);
}
}
private void verifyIntegerPropertySetter(CarPropertyConfig<T> carPropertyConfig,
CarPropertyManager carPropertyManager) {
if (mPropertyId == VehiclePropertyIds.HVAC_FAN_DIRECTION) {
for (int areaId : carPropertyConfig.getAreaIds()) {
int[] availableHvacFanDirections = carPropertyManager.getIntArrayProperty(
VehiclePropertyIds.HVAC_FAN_DIRECTION_AVAILABLE, areaId);
for (int availableHvacFanDirection : availableHvacFanDirections) {
verifySetProperty(carPropertyConfig, carPropertyManager, areaId,
(T) Integer.valueOf(availableHvacFanDirection));
}
}
} else if (mVerifySetterWithConfigArrayValues) {
verifySetterWithValues((CarPropertyConfig<T>) carPropertyConfig, carPropertyManager,
(Collection<T>) carPropertyConfig.getConfigArray());
} else if (!mPossibleCarPropertyValues.isEmpty()) {
verifySetterWithValues((CarPropertyConfig<T>) carPropertyConfig, carPropertyManager,
mPossibleCarPropertyValues);
} else {
verifySetterWithMinMaxValues(carPropertyConfig, carPropertyManager);
}
}
private void verifySetterWithValues(CarPropertyConfig<T> carPropertyConfig,
CarPropertyManager carPropertyManager, Collection<T> valuesToSet) {
for (T valueToSet : valuesToSet) {
for (int areaId : carPropertyConfig.getAreaIds()) {
verifySetProperty(carPropertyConfig, carPropertyManager, areaId, valueToSet);
}
}
}
private void verifySetterWithMinMaxValues(CarPropertyConfig<T> carPropertyConfig,
CarPropertyManager carPropertyManager) {
for (int areaId : carPropertyConfig.getAreaIds()) {
if (carPropertyConfig.getMinValue(areaId) == null || carPropertyConfig.getMaxValue(
areaId) == null) {
continue;
}
List<Integer> valuesToSet = IntStream.rangeClosed(
((Integer) carPropertyConfig.getMinValue(areaId)).intValue(),
((Integer) carPropertyConfig.getMaxValue(areaId)).intValue()).boxed().collect(
Collectors.toList());
for (Integer valueToSet : valuesToSet) {
verifySetProperty(carPropertyConfig, carPropertyManager, areaId, (T) valueToSet);
}
}
}
private void verifyFloatPropertySetter(CarPropertyConfig<T> carPropertyConfig,
CarPropertyManager carPropertyManager) {
if (!mPossibleCarPropertyValues.isEmpty()) {
verifySetterWithValues((CarPropertyConfig<T>) carPropertyConfig, carPropertyManager,
mPossibleCarPropertyValues);
}
}
private void verifySetProperty(CarPropertyConfig<T> carPropertyConfig,
CarPropertyManager carPropertyManager, int areaId, T valueToSet) {
CarPropertyValue<T> currentCarPropertyValue = carPropertyManager.getProperty(mPropertyId,
areaId);
verifyCarPropertyValue(carPropertyConfig, currentCarPropertyValue, areaId,
CAR_PROPERTY_VALUE_SOURCE_GETTER);
if (valueEquals(valueToSet, currentCarPropertyValue.getValue())) {
return;
}
CarPropertyValue<T> updatedCarPropertyValue = setPropertyAndWaitForChange(
carPropertyManager, mPropertyId, carPropertyConfig.getPropertyType(), areaId,
valueToSet);
verifyCarPropertyValue(carPropertyConfig, updatedCarPropertyValue, areaId,
CAR_PROPERTY_VALUE_SOURCE_CALLBACK);
}
private void verifySetNotAvailable(CarPropertyConfig<T> carPropertyConfig,
CarPropertyManager carPropertyManager) {
if (carPropertyConfig.getAccess() != CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE) {
return;
}
for (int areaId : carPropertyConfig.getAreaIds()) {
CarPropertyValue<T> currentValue = null;
try {
// getProperty may/may not throw exception when the property is not available.
currentValue = carPropertyManager.getProperty(mPropertyId, areaId);
T valueToSet = getDefaultValue(mPropertyType);
verifySetProperty(carPropertyConfig, carPropertyManager, areaId, valueToSet);
} catch (Exception e) {
// In normal cases, this should throw PropertyNotAvailableException.
// In rare cases, the value we are setting is the same as the current value,
// which makes the set operation a no-op. So it is possible that no exception
// is thrown here.
// It is also possible that this may throw IllegalArgumentException if the value to
// set is not valid.
assertWithMessage("If exception is thrown for setting hvac property that is not "
+ "available, the exception type is correct").that(
e.getClass()).isAnyOf(PropertyNotAvailableException.class,
IllegalArgumentException.class);
}
if (currentValue == null) {
// If the property is not available for getting, continue.
continue;
}
CarPropertyValue<T> newValue = carPropertyManager.getProperty(mPropertyId, areaId);
assertWithMessage("setting HVAC dependent property: " + mPropertyName
+ " while hvac power is off must have no effect").that(newValue.getValue())
.isEqualTo(currentValue.getValue());
}
}
private void verifyCarPropertyValueCallback(CarPropertyConfig<T> carPropertyConfig,
CarPropertyManager carPropertyManager) {
if (carPropertyConfig.getAccess() == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE) {
return;
}
int updatesPerAreaId = 1;
long timeoutMillis = 1500;
if (mChangeMode == CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS) {
updatesPerAreaId = 2;
float secondsToMillis = 1_000;
long bufferMillis = 1_000; // 1 second
timeoutMillis = ((long) ((1.0f / carPropertyConfig.getMinSampleRate()) * secondsToMillis
* updatesPerAreaId)) + bufferMillis;
}
CarPropertyValueCallback carPropertyValueCallback = new CarPropertyValueCallback(
mPropertyName, carPropertyConfig.getAreaIds(), updatesPerAreaId, timeoutMillis);
assertWithMessage("Failed to register callback for " + mPropertyName).that(
carPropertyManager.registerCallback(carPropertyValueCallback, mPropertyId,
carPropertyConfig.getMaxSampleRate())).isTrue();
SparseArray<List<CarPropertyValue<?>>> areaIdToCarPropertyValues =
carPropertyValueCallback.getAreaIdToCarPropertyValues();
carPropertyManager.unregisterCallback(carPropertyValueCallback, mPropertyId);
for (int areaId : carPropertyConfig.getAreaIds()) {
List<CarPropertyValue<?>> carPropertyValues = areaIdToCarPropertyValues.get(areaId);
assertWithMessage(
mPropertyName + " callback value list is null for area ID: " + areaId).that(
carPropertyValues).isNotNull();
assertWithMessage(mPropertyName + " callback values did not receive " + updatesPerAreaId
+ " updates for area ID: " + areaId).that(carPropertyValues.size()).isAtLeast(
updatesPerAreaId);
for (CarPropertyValue<?> carPropertyValue : carPropertyValues) {
verifyCarPropertyValue(carPropertyConfig, carPropertyValue,
carPropertyValue.getAreaId(), CAR_PROPERTY_VALUE_SOURCE_CALLBACK);
}
}
}
private void verifyCarPropertyConfig(CarPropertyConfig<T> carPropertyConfig) {
assertWithMessage(mPropertyName + " CarPropertyConfig must have correct property ID")
.that(carPropertyConfig.getPropertyId())
.isEqualTo(mPropertyId);
if (mAccess == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE) {
assertWithMessage(
mPropertyName
+ " must be "
+ accessToString(CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ)
+ ", "
+ accessToString(
CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE)
+ ", or "
+ accessToString(
CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE))
.that(carPropertyConfig.getAccess())
.isIn(
ImmutableSet.of(
CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE,
CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE));
} else {
assertWithMessage(mPropertyName + " must be " + accessToString(mAccess))
.that(carPropertyConfig.getAccess())
.isEqualTo(mAccess);
}
assertWithMessage(mPropertyName + " must be " + areaTypeToString(mAreaType))
.that(carPropertyConfig.getAreaType())
.isEqualTo(mAreaType);
assertWithMessage(mPropertyName + " must be " + changeModeToString(mChangeMode))
.that(carPropertyConfig.getChangeMode())
.isEqualTo(mChangeMode);
assertWithMessage(mPropertyName + " must be " + mPropertyType + " type property")
.that(carPropertyConfig.getPropertyType())
.isEqualTo(mPropertyType);
assertWithMessage(mPropertyName + "'s must have at least 1 area ID defined").that(
carPropertyConfig.getAreaIds().length).isAtLeast(1);
assertWithMessage(mPropertyName + "'s area IDs must all be unique: " + Arrays.toString(
carPropertyConfig.getAreaIds())).that(ImmutableSet.copyOf(Arrays.stream(
carPropertyConfig.getAreaIds()).boxed().collect(Collectors.toList())).size()
== carPropertyConfig.getAreaIds().length).isTrue();
if (mAreaType == VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL) {
assertWithMessage(
mPropertyName
+ "'s AreaIds must contain a single 0 since it is "
+ areaTypeToString(mAreaType))
.that(carPropertyConfig.getAreaIds())
.isEqualTo(new int[] {0});
} else if (mAreaType == VehicleAreaType.VEHICLE_AREA_TYPE_WHEEL) {
verifyValidAreaIdsForAreaType(carPropertyConfig, ALL_POSSIBLE_WHEEL_AREA_IDS);
verifyNoAreaOverlapInAreaIds(carPropertyConfig, WHEEL_AREAS);
} else if (mAreaType == VehicleAreaType.VEHICLE_AREA_TYPE_WINDOW) {
verifyValidAreaIdsForAreaType(carPropertyConfig, ALL_POSSIBLE_WINDOW_AREA_IDS);
verifyNoAreaOverlapInAreaIds(carPropertyConfig, WINDOW_AREAS);
} else if (mAreaType == VehicleAreaType.VEHICLE_AREA_TYPE_MIRROR) {
verifyValidAreaIdsForAreaType(carPropertyConfig, ALL_POSSIBLE_MIRROR_AREA_IDS);
verifyNoAreaOverlapInAreaIds(carPropertyConfig, MIRROR_AREAS);
} else if (mAreaType == VehicleAreaType.VEHICLE_AREA_TYPE_SEAT
&& mPropertyId != VehiclePropertyIds.INFO_DRIVER_SEAT) {
verifyValidAreaIdsForAreaType(carPropertyConfig, ALL_POSSIBLE_SEAT_AREA_IDS);
verifyNoAreaOverlapInAreaIds(carPropertyConfig, SEAT_AREAS);
} else if (mAreaType == VehicleAreaType.VEHICLE_AREA_TYPE_DOOR) {
verifyValidAreaIdsForAreaType(carPropertyConfig, ALL_POSSIBLE_DOOR_AREA_IDS);
verifyNoAreaOverlapInAreaIds(carPropertyConfig, DOOR_AREAS);
}
if (mAreaIdsVerifier.isPresent()) {
mAreaIdsVerifier.get().verify(carPropertyConfig.getAreaIds());
}
if (mChangeMode == CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS) {
verifyContinuousCarPropertyConfig(carPropertyConfig);
} else {
verifyNonContinuousCarPropertyConfig(carPropertyConfig);
}
mCarPropertyConfigVerifier.ifPresent(
carPropertyConfigVerifier -> carPropertyConfigVerifier.verify(carPropertyConfig));
if (!mPossibleConfigArrayValues.isEmpty()) {
assertWithMessage(mPropertyName + " configArray must specify supported values")
.that(carPropertyConfig.getConfigArray().size())
.isGreaterThan(0);
for (Integer supportedValue : carPropertyConfig.getConfigArray()) {
assertWithMessage(
mPropertyName
+ " configArray value must be a defined "
+ "value: "
+ supportedValue)
.that(supportedValue)
.isIn(mPossibleConfigArrayValues);
}
}
mConfigArrayVerifier.ifPresent(configArrayVerifier -> configArrayVerifier.verify(
carPropertyConfig.getConfigArray()));
if (mPossibleConfigArrayValues.isEmpty() && !mConfigArrayVerifier.isPresent()
&& !mCarPropertyConfigVerifier.isPresent()) {
assertWithMessage(mPropertyName + " configArray is undefined, so it must be empty")
.that(carPropertyConfig.getConfigArray().size())
.isEqualTo(0);
}
for (int areaId : carPropertyConfig.getAreaIds()) {
// TODO(b/261480597): verify AreaIdConfig#getSupportedEnumValues()
T areaIdMinValue = (T) carPropertyConfig.getMinValue(areaId);
T areaIdMaxValue = (T) carPropertyConfig.getMaxValue(areaId);
if (mRequireMinMaxValues) {
assertWithMessage(mPropertyName + " - area ID: " + areaId
+ " must have min value defined").that(areaIdMinValue).isNotNull();
assertWithMessage(mPropertyName + " - area ID: " + areaId
+ " must have max value defined").that(areaIdMaxValue).isNotNull();
}
if (mRequireMinValuesToBeZero) {
assertWithMessage(
mPropertyName + " - area ID: " + areaId + " min value must be zero").that(
areaIdMinValue).isEqualTo(0);
}
if (mRequireZeroToBeContainedInMinMaxRanges) {
assertWithMessage(mPropertyName + " - areaId: " + areaId
+ "'s max and min range must contain zero").that(
verifyMaxAndMinRangeContainsZero(areaIdMinValue, areaIdMaxValue)).isTrue();
}
if (areaIdMinValue == null || areaIdMaxValue == null) {
continue;
}
assertWithMessage(
mPropertyName
+ " - areaId: "
+ areaId
+ "'s max value must be >= min value")
.that(verifyMaxAndMin(areaIdMinValue, areaIdMaxValue))
.isTrue();
}
}
private boolean verifyMaxAndMinRangeContainsZero(T min, T max) {
int propertyType = mPropertyId & VehiclePropertyType.MASK;
switch (propertyType) {
case VehiclePropertyType.INT32:
return (Integer) max >= 0 && (Integer) min <= 0;
case VehiclePropertyType.INT64:
return (Long) max >= 0 && (Long) min <= 0;
case VehiclePropertyType.FLOAT:
return (Float) max >= 0 && (Float) min <= 0;
default:
return false;
}
}
private boolean verifyMaxAndMin(T min, T max) {
int propertyType = mPropertyId & VehiclePropertyType.MASK;
switch (propertyType) {
case VehiclePropertyType.INT32:
return (Integer) max >= (Integer) min;
case VehiclePropertyType.INT64:
return (Long) max >= (Long) min;
case VehiclePropertyType.FLOAT:
return (Float) max >= (Float) min;
default:
return false;
}
}
private void verifyContinuousCarPropertyConfig(CarPropertyConfig<T> carPropertyConfig) {
assertWithMessage(
mPropertyName
+ " must define max sample rate since change mode is "
+ changeModeToString(mChangeMode))
.that(carPropertyConfig.getMaxSampleRate())
.isGreaterThan(0);
assertWithMessage(
mPropertyName
+ " must define min sample rate since change mode is "
+ changeModeToString(mChangeMode))
.that(carPropertyConfig.getMinSampleRate())
.isGreaterThan(0);
assertWithMessage(mPropertyName + " max sample rate must be >= min sample rate")
.that(carPropertyConfig.getMaxSampleRate() >= carPropertyConfig.getMinSampleRate())
.isTrue();
}
private void verifyNonContinuousCarPropertyConfig(CarPropertyConfig<T> carPropertyConfig) {
assertWithMessage(
mPropertyName
+ " must define max sample rate as 0 since change mode is "
+ changeModeToString(mChangeMode))
.that(carPropertyConfig.getMaxSampleRate())
.isEqualTo(0);
assertWithMessage(
mPropertyName
+ " must define min sample rate as 0 since change mode is "
+ changeModeToString(mChangeMode))
.that(carPropertyConfig.getMinSampleRate())
.isEqualTo(0);
}
private void verifyCarPropertyValueGetter(CarPropertyConfig<T> carPropertyConfig,
CarPropertyManager carPropertyManager) {
if (carPropertyConfig.getAccess() == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE) {
return;
}
for (int areaId : carPropertyConfig.getAreaIds()) {
CarPropertyValue<?> carPropertyValue =
carPropertyManager.getProperty(mPropertyId, areaId);
verifyCarPropertyValue(carPropertyConfig, carPropertyValue, areaId,
CAR_PROPERTY_VALUE_SOURCE_GETTER);
}
}
private void verifyCarPropertyValue(CarPropertyConfig<T> carPropertyConfig,
CarPropertyValue<?> carPropertyValue, int expectedAreaId, String source) {
// TODO(b/258512284): update CarPropertyValueVerifier to test GetPropertyResult as well.
mCarPropertyValueVerifier.ifPresent(
propertyValueVerifier -> propertyValueVerifier.verify(carPropertyConfig,
carPropertyValue));
verifyCarPropertyValue(carPropertyConfig, carPropertyValue.getPropertyId(),
carPropertyValue.getAreaId(), carPropertyValue.getStatus(),
carPropertyValue.getTimestamp(), (T) carPropertyValue.getValue(), expectedAreaId,
source);
}
private void verifyCarPropertyValue(CarPropertyConfig<T> carPropertyConfig,
int propertyId, int areaId, int status, long timestampNanos, T value,
int expectedAreaId, String source) {
assertWithMessage(
mPropertyName
+ " - areaId: "
+ areaId
+ " - source: "
+ source
+ " value must have correct property ID")
.that(propertyId)
.isEqualTo(mPropertyId);
assertWithMessage(
mPropertyName
+ " - areaId: "
+ areaId
+ " - source: "
+ source
+ " value must have correct area id: "
+ areaId)
.that(areaId)
.isEqualTo(expectedAreaId);
assertWithMessage(mPropertyName + " - areaId: " + areaId + " - source: " + source
+ " area ID must be in carPropertyConfig#getAreaIds()").that(Arrays.stream(
carPropertyConfig.getAreaIds()).boxed().collect(Collectors.toList()).contains(
areaId)).isTrue();
assertWithMessage(
mPropertyName
+ " - areaId: "
+ areaId
+ " - source: "
+ source
+ " value must have AVAILABLE status")
.that(status)
.isEqualTo(CarPropertyValue.STATUS_AVAILABLE);
assertWithMessage(
mPropertyName
+ " - areaId: "
+ areaId
+ " - source: "
+ source
+ " timestamp must use the SystemClock.elapsedRealtimeNanos() time"
+ " base")
.that(timestampNanos)
.isAtLeast(0);
assertWithMessage(
mPropertyName
+ " - areaId: "
+ areaId
+ " - source: "
+ source
+ " timestamp must use the SystemClock.elapsedRealtimeNanos() time"
+ " base")
.that(timestampNanos)
.isLessThan(SystemClock.elapsedRealtimeNanos());
assertWithMessage(
mPropertyName
+ " - areaId: "
+ areaId
+ " - source: "
+ source
+ " must return "
+ mPropertyType
+ " type value")
.that(value.getClass())
.isEqualTo(mPropertyType);
if (mRequirePropertyValueToBeInConfigArray) {
assertWithMessage(
mPropertyName
+ " - areaId: "
+ areaId
+ " - source: "
+ source
+ " value must be listed in configArray,"
+ " configArray:")
.that(carPropertyConfig.getConfigArray())
.contains(value);
}
if (!mPossibleCarPropertyValues.isEmpty()) {
if (Float.class.equals(mPropertyType)) {
boolean foundInPossibleValues = false;
for (Float possibleValue : (Collection<Float>) mPossibleCarPropertyValues) {
if (floatEquals(possibleValue, (Float) value)) {
foundInPossibleValues = true;
break;
}
}
assertWithMessage(
mPropertyName + " - areaId: " + areaId + " - source: " + source + " value: "
+ value + " must be listed in the Float set: "
+ mPossibleCarPropertyValues).that(foundInPossibleValues).isTrue();
} else {
assertWithMessage(mPropertyName + " - areaId: " + areaId + " - source: " + source
+ " value must be listed in the set").that(
value).isIn(mPossibleCarPropertyValues);
}
}
T areaIdMinValue = (T) carPropertyConfig.getMinValue(areaId);
T areaIdMaxValue = (T) carPropertyConfig.getMaxValue(areaId);
if (areaIdMinValue != null && areaIdMaxValue != null) {
assertWithMessage(
"Property value: " + value + " must be between the max: "
+ areaIdMaxValue + " and min: " + areaIdMinValue
+ " values for area ID: " + Integer.toHexString(areaId)).that(
verifyValueInRange(
areaIdMinValue,
areaIdMaxValue,
(T) value))
.isTrue();
}
}
private boolean verifyValueInRange(T min, T max, T value) {
int propertyType = mPropertyId & VehiclePropertyType.MASK;
switch (propertyType) {
case VehiclePropertyType.INT32:
return ((Integer) value >= (Integer) min && (Integer) value <= (Integer) max);
case VehiclePropertyType.INT64:
return ((Long) value >= (Long) min && (Long) value <= (Long) max);
case VehiclePropertyType.FLOAT:
return ((Float) value >= (Float) min && (Float) value <= (Float) max);
default:
return false;
}
}
private static ImmutableSet<Integer> generateAllPossibleAreaIds(ImmutableSet<Integer> areas) {
ImmutableSet.Builder<Integer> allPossibleAreaIdsBuilder = ImmutableSet.builder();
for (int i = 1; i <= areas.size(); i++) {
allPossibleAreaIdsBuilder.addAll(Sets.combinations(areas, i).stream().map(areaCombo -> {
Integer possibleAreaId = 0;
for (Integer area : areaCombo) {
possibleAreaId |= area;
}
return possibleAreaId;
}).collect(Collectors.toList()));
}
return allPossibleAreaIdsBuilder.build();
}
private void verifyValidAreaIdsForAreaType(CarPropertyConfig<T> carPropertyConfig,
ImmutableSet<Integer> allPossibleAreaIds) {
for (int areaId : carPropertyConfig.getAreaIds()) {
assertWithMessage(
mPropertyName + "'s area ID must be a valid " + areaTypeToString(mAreaType)
+ " area ID").that(areaId).isIn(allPossibleAreaIds);
}
}
private void verifyNoAreaOverlapInAreaIds(CarPropertyConfig<T> carPropertyConfig,
ImmutableSet<Integer> areas) {
if (carPropertyConfig.getAreaIds().length < 2) {
return;
}
ImmutableSet<Integer> areaIds = ImmutableSet.copyOf(Arrays.stream(
carPropertyConfig.getAreaIds()).boxed().collect(Collectors.toList()));
List<Integer> areaIdOverlapCheckResults = Sets.combinations(areaIds, 2).stream().map(
areaIdPair -> {
List<Integer> areaIdPairAsList = areaIdPair.stream().collect(
Collectors.toList());
return areaIdPairAsList.get(0) & areaIdPairAsList.get(1);
}).collect(Collectors.toList());
assertWithMessage(
mPropertyName + " area IDs: " + Arrays.toString(carPropertyConfig.getAreaIds())
+ " must contain each area only once (e.g. no bitwise AND overlap) for "
+ "the area type: " + areaTypeToString(mAreaType)).that(
Collections.frequency(areaIdOverlapCheckResults, 0)
== areaIdOverlapCheckResults.size()).isTrue();
}
private void verifyPermissionNotGrantedException(CarPropertyConfig<T> carPropertyConfig,
CarPropertyManager carPropertyManager) {
assertWithMessage(
mPropertyName
+ " - property ID: "
+ mPropertyId
+ " CarPropertyConfig should not be accessible without permissions")
.that(carPropertyManager.getCarPropertyConfig(mPropertyId))
.isNull();
for (int areaId : carPropertyConfig.getAreaIds()) {
switch(carPropertyConfig.getAccess()) {
case CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE:
assertThrows(
mPropertyName
+ " - property ID: "
+ mPropertyId
+ " - area ID: "
+ areaId
+ " should not be able to be read without permissions",
SecurityException.class,
() -> carPropertyManager.getProperty(mPropertyId, areaId));
assertThrows(
mPropertyName
+ " - property ID: "
+ mPropertyId
+ " - area ID: "
+ areaId
+ " should not be able to be written to without permissions",
SecurityException.class,
() -> carPropertyManager.setProperty(
Object.class, mPropertyId, areaId, 0));
break;
case CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ:
assertThrows(
mPropertyName
+ " - property ID: "
+ mPropertyId
+ " - area ID: "
+ areaId
+ " should not be able to be read without permissions",
SecurityException.class,
() -> carPropertyManager.getProperty(mPropertyId, areaId));
break;
case CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE:
assertThrows(
mPropertyName
+ " - property ID: "
+ mPropertyId
+ " - area ID: "
+ areaId
+ " should not be able to be written to without permissions",
SecurityException.class,
() -> carPropertyManager.setProperty(
Object.class, mPropertyId, areaId, 0));
break;
}
}
}
public interface ConfigArrayVerifier {
void verify(List<Integer> configArray);
}
public interface CarPropertyValueVerifier {
void verify(CarPropertyConfig<?> carPropertyConfig, CarPropertyValue<?> carPropertyValue);
}
public interface AreaIdsVerifier {
void verify(int[] areaIds);
}
public interface CarPropertyConfigVerifier {
void verify(CarPropertyConfig<?> carPropertyConfig);
}
public static class Builder<T> {
private final int mPropertyId;
private final int mAccess;
private final int mAreaType;
private final int mChangeMode;
private final Class<T> mPropertyType;
private boolean mRequiredProperty = false;
private Optional<ConfigArrayVerifier> mConfigArrayVerifier = Optional.empty();
private Optional<CarPropertyValueVerifier> mCarPropertyValueVerifier = Optional.empty();
private Optional<AreaIdsVerifier> mAreaIdsVerifier = Optional.empty();
private Optional<CarPropertyConfigVerifier> mCarPropertyConfigVerifier = Optional.empty();
private ImmutableSet<Integer> mPossibleConfigArrayValues = ImmutableSet.of();
private ImmutableSet<T> mPossibleCarPropertyValues = ImmutableSet.of();
private boolean mRequirePropertyValueToBeInConfigArray = false;
private boolean mVerifySetterWithConfigArrayValues = false;
private boolean mRequireMinMaxValues = false;
private boolean mRequireMinValuesToBeZero = false;
private boolean mRequireZeroToBeContainedInMinMaxRanges = false;
private boolean mPossiblyDependentOnHvacPowerOn = false;
private final ImmutableSet.Builder<String> mReadPermissionsBuilder = ImmutableSet.builder();
private final ImmutableSet.Builder<String> mWritePermissionsBuilder =
ImmutableSet.builder();
private Builder(int propertyId, int access, int areaType, int changeMode,
Class<T> propertyType) {
mPropertyId = propertyId;
mAccess = access;
mAreaType = areaType;
mChangeMode = changeMode;
mPropertyType = propertyType;
}
public Builder<T> requireProperty() {
mRequiredProperty = true;
return this;
}
public Builder<T> setConfigArrayVerifier(ConfigArrayVerifier configArrayVerifier) {
mConfigArrayVerifier = Optional.of(configArrayVerifier);
return this;
}
public Builder<T> setCarPropertyValueVerifier(
CarPropertyValueVerifier carPropertyValueVerifier) {
mCarPropertyValueVerifier = Optional.of(carPropertyValueVerifier);
return this;
}
public Builder<T> setAreaIdsVerifier(AreaIdsVerifier areaIdsVerifier) {
mAreaIdsVerifier = Optional.of(areaIdsVerifier);
return this;
}
public Builder<T> setCarPropertyConfigVerifier(
CarPropertyConfigVerifier carPropertyConfigVerifier) {
mCarPropertyConfigVerifier = Optional.of(carPropertyConfigVerifier);
return this;
}
public Builder<T> setPossibleConfigArrayValues(
ImmutableSet<Integer> possibleConfigArrayValues) {
mPossibleConfigArrayValues = possibleConfigArrayValues;
return this;
}
public Builder<T> setPossibleCarPropertyValues(ImmutableSet<T> possibleCarPropertyValues) {
mPossibleCarPropertyValues = possibleCarPropertyValues;
return this;
}
public Builder<T> requirePropertyValueTobeInConfigArray() {
mRequirePropertyValueToBeInConfigArray = true;
return this;
}
public Builder<T> verifySetterWithConfigArrayValues() {
mVerifySetterWithConfigArrayValues = true;
return this;
}
public Builder<T> requireMinMaxValues() {
mRequireMinMaxValues = true;
return this;
}
public Builder<T> requireMinValuesToBeZero() {
mRequireMinValuesToBeZero = true;
return this;
}
public Builder<T> requireZeroToBeContainedInMinMaxRanges() {
mRequireZeroToBeContainedInMinMaxRanges = true;
return this;
}
public Builder<T> setPossiblyDependentOnHvacPowerOn() {
mPossiblyDependentOnHvacPowerOn = true;
return this;
}
public Builder<T> addReadPermission(String readPermission) {
mReadPermissionsBuilder.add(readPermission);
return this;
}
public Builder<T> addWritePermission(String writePermission) {
mWritePermissionsBuilder.add(writePermission);
return this;
}
public VehiclePropertyVerifier<T> build() {
return new VehiclePropertyVerifier<>(
mPropertyId,
mAccess,
mAreaType,
mChangeMode,
mPropertyType,
mRequiredProperty,
mConfigArrayVerifier,
mCarPropertyValueVerifier,
mAreaIdsVerifier,
mCarPropertyConfigVerifier,
mPossibleConfigArrayValues,
mPossibleCarPropertyValues,
mRequirePropertyValueToBeInConfigArray,
mVerifySetterWithConfigArrayValues,
mRequireMinMaxValues,
mRequireMinValuesToBeZero,
mRequireZeroToBeContainedInMinMaxRanges,
mPossiblyDependentOnHvacPowerOn,
mReadPermissionsBuilder.build(),
mWritePermissionsBuilder.build());
}
}
private static class CarPropertyValueCallback implements
CarPropertyManager.CarPropertyEventCallback {
private final String mPropertyName;
private final int[] mAreaIds;
private final int mTotalCarPropertyValuesPerAreaId;
private final CountDownLatch mCountDownLatch = new CountDownLatch(1);
private final Object mLock = new Object();
@GuardedBy("mLock")
private final SparseArray<List<CarPropertyValue<?>>> mAreaIdToCarPropertyValues =
new SparseArray<>();
private final long mTimeoutMillis;
CarPropertyValueCallback(String propertyName, int[] areaIds,
int totalCarPropertyValuesPerAreaId, long timeoutMillis) {
mPropertyName = propertyName;
mAreaIds = areaIds;
mTotalCarPropertyValuesPerAreaId = totalCarPropertyValuesPerAreaId;
mTimeoutMillis = timeoutMillis;
synchronized (mLock) {
for (int areaId : mAreaIds) {
mAreaIdToCarPropertyValues.put(areaId, new ArrayList<>());
}
}
}
public SparseArray<List<CarPropertyValue<?>>> getAreaIdToCarPropertyValues() {
boolean awaitSuccess = false;
try {
awaitSuccess = mCountDownLatch.await(mTimeoutMillis, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
assertWithMessage("Waiting for onChangeEvent callback(s) for " + mPropertyName
+ " threw an exception: " + e).fail();
}
synchronized (mLock) {
assertWithMessage("Never received " + mTotalCarPropertyValuesPerAreaId
+ " CarPropertyValues for all " + mPropertyName + "'s areaIds: "
+ Arrays.toString(mAreaIds) + " before " + mTimeoutMillis + " ms timeout - "
+ mAreaIdToCarPropertyValues).that(awaitSuccess).isTrue();
return mAreaIdToCarPropertyValues.clone();
}
}
@Override
public void onChangeEvent(CarPropertyValue carPropertyValue) {
synchronized (mLock) {
if (hasEnoughCarPropertyValuesForEachAreaIdLocked()) {
return;
}
mAreaIdToCarPropertyValues.get(carPropertyValue.getAreaId()).add(carPropertyValue);
if (hasEnoughCarPropertyValuesForEachAreaIdLocked()) {
mCountDownLatch.countDown();
}
}
}
@GuardedBy("mLock")
private boolean hasEnoughCarPropertyValuesForEachAreaIdLocked() {
for (int areaId : mAreaIds) {
List<CarPropertyValue<?>> carPropertyValues = mAreaIdToCarPropertyValues.get(
areaId);
if (carPropertyValues == null
|| carPropertyValues.size() < mTotalCarPropertyValuesPerAreaId) {
return false;
}
}
return true;
}
@Override
public void onErrorEvent(int propId, int zone) {
}
@Override
public void onErrorEvent(int propId, int areaId, int errorCode) {
}
}
private static class SetterCallback<T> implements CarPropertyManager.CarPropertyEventCallback {
private final int mPropertyId;
private final String mPropertyName;
private final int mAreaId;
private final T mExpectedSetValue;
private final CountDownLatch mCountDownLatch = new CountDownLatch(1);
private final long mCreationTimeNanos = SystemClock.elapsedRealtimeNanos();
private CarPropertyValue<?> mUpdatedCarPropertyValue = null;
SetterCallback(int propertyId, int areaId, T expectedSetValue) {
mPropertyId = propertyId;
mPropertyName = VehiclePropertyIds.toString(propertyId);
mAreaId = areaId;
mExpectedSetValue = expectedSetValue;
}
public CarPropertyValue<?> waitForUpdatedCarPropertyValue() {
try {
assertWithMessage(
"Never received onChangeEvent(s) for " + mPropertyName + " new value: "
+ mExpectedSetValue + " before 5s timeout").that(
mCountDownLatch.await(5, TimeUnit.SECONDS)).isTrue();
} catch (InterruptedException e) {
assertWithMessage("Waiting for onChangeEvent set callback for "
+ mPropertyName + " threw an exception: " + e).fail();
}
return mUpdatedCarPropertyValue;
}
@Override
public void onChangeEvent(CarPropertyValue carPropertyValue) {
if (mUpdatedCarPropertyValue != null || carPropertyValue.getPropertyId() != mPropertyId
|| carPropertyValue.getAreaId() != mAreaId
|| carPropertyValue.getStatus() != CarPropertyValue.STATUS_AVAILABLE
|| carPropertyValue.getTimestamp() <= mCreationTimeNanos
|| carPropertyValue.getTimestamp() >= SystemClock.elapsedRealtimeNanos()
|| !valueEquals(mExpectedSetValue, (T) carPropertyValue.getValue())) {
return;
}
mUpdatedCarPropertyValue = carPropertyValue;
mCountDownLatch.countDown();
}
@Override
public void onErrorEvent(int propId, int zone) {
}
}
private static <V> boolean valueEquals(V v1, V v2) {
return (v1 instanceof Float && floatEquals((Float) v1, (Float) v2)) || v1.equals(v2);
}
private static boolean floatEquals(float f1, float f2) {
return Math.abs(f1 - f2) < FLOAT_INEQUALITY_THRESHOLD;
}
private class CarPropertyCallback implements GetPropertyCallback {
private final CountDownLatch mCountDownLatch;
private final int mGetPropertyResultsCount;
private final Object mLock = new Object();
@GuardedBy("mLock")
private List<GetPropertyResult<?>> mGetPropertyResults;
public List<GetPropertyResult<?>> waitForGetPropertyResults() {
try {
assertWithMessage("Received " + (mGetPropertyResultsCount
- mCountDownLatch.getCount()) + " onSuccess(s), expected "
+ mGetPropertyResultsCount + " onSuccess(s)").that(mCountDownLatch.await(
5, TimeUnit.SECONDS)).isTrue();
} catch (InterruptedException e) {
assertWithMessage("Waiting for onSuccess threw an exception: " + e
).fail();
}
return mGetPropertyResults;
}
@Override
public void onSuccess(GetPropertyResult getPropertyResult) {
synchronized (mLock) {
mGetPropertyResults.add(getPropertyResult);
mCountDownLatch.countDown();
}
}
@Override
public void onFailure(PropertyAsyncError getPropertyError) {
assertWithMessage("PropertyAsyncError with requestId "
+ getPropertyError.getRequestId() + " returned with error code: "
+ getPropertyError.getErrorCode()).fail();
}
CarPropertyCallback(int getPropertyResultsCount) {
mCountDownLatch = new CountDownLatch(getPropertyResultsCount);
mGetPropertyResults = new ArrayList<>();
mGetPropertyResultsCount = getPropertyResultsCount;
}
}
private void verifyGetPropertiesAsync(CarPropertyConfig<T> carPropertyConfig,
CarPropertyManager carPropertyManager) {
if (carPropertyConfig.getAccess() != CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE
&& carPropertyConfig.getAccess()
!= CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ) {
return;
}
List<GetPropertyRequest> getPropertyRequests = new ArrayList<>();
SparseIntArray requestIdToAreaIdMap = new SparseIntArray();
for (int areaId : carPropertyConfig.getAreaIds()) {
GetPropertyRequest getPropertyRequest = carPropertyManager.generateGetPropertyRequest(
mPropertyId, areaId);
int requestId = getPropertyRequest.getRequestId();
requestIdToAreaIdMap.put(requestId, areaId);
getPropertyRequests.add(getPropertyRequest);
}
CarPropertyCallback carPropertyCallback = new CarPropertyCallback(
requestIdToAreaIdMap.size());
carPropertyManager.getPropertiesAsync(getPropertyRequests, /* cancellationSignal: */ null,
/* callbackExecutor: */ null, carPropertyCallback);
List<GetPropertyResult<?>> getPropertyResults =
carPropertyCallback.waitForGetPropertyResults();
for (GetPropertyResult<?> getPropertyResult : getPropertyResults) {
int requestId = getPropertyResult.getRequestId();
int propertyId = getPropertyResult.getPropertyId();
if (requestIdToAreaIdMap.indexOfKey(requestId) < 0) {
assertWithMessage("getPropertiesAsync received unknown requestId "
+ requestId + " with property ID "
+ VehiclePropertyIds.toString(propertyId)).fail();
}
Integer expectedAreaId = requestIdToAreaIdMap.get(requestId);
verifyCarPropertyValue(carPropertyConfig, propertyId, getPropertyResult.getAreaId(),
CarPropertyValue.STATUS_AVAILABLE, getPropertyResult.getTimestampNanos(),
(T) getPropertyResult.getValue(), expectedAreaId,
CAR_PROPERTY_VALUE_SOURCE_CALLBACK);
}
}
private static <U> CarPropertyValue<U> setPropertyAndWaitForChange(
CarPropertyManager carPropertyManager, int propertyId, Class<U> propertyType,
int areaId, U valueToSet) {
SetterCallback setterCallback = new SetterCallback(propertyId, areaId, valueToSet);
assertWithMessage("Failed to register setter callback for " + VehiclePropertyIds.toString(
propertyId)).that(carPropertyManager.registerCallback(setterCallback, propertyId,
CarPropertyManager.SENSOR_RATE_FASTEST)).isTrue();
carPropertyManager.setProperty(propertyType, propertyId, areaId, valueToSet);
CarPropertyValue<U> carPropertyValue = setterCallback.waitForUpdatedCarPropertyValue();
carPropertyManager.unregisterCallback(setterCallback, propertyId);
return carPropertyValue;
}
}