1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.car.cts.utils; 18 19 import static android.car.cts.utils.ShellPermissionUtils.runWithShellPermissionIdentity; 20 import static android.content.pm.PackageManager.PERMISSION_GRANTED; 21 22 import static com.google.common.truth.Truth.assertThat; 23 import static com.google.common.truth.Truth.assertWithMessage; 24 25 import static org.junit.Assert.assertThrows; 26 import static org.junit.Assume.assumeThat; 27 import static org.junit.Assume.assumeTrue; 28 29 import android.car.VehicleAreaDoor; 30 import android.car.VehicleAreaMirror; 31 import android.car.VehicleAreaSeat; 32 import android.car.VehicleAreaType; 33 import android.car.VehicleAreaWheel; 34 import android.car.VehicleAreaWindow; 35 import android.car.VehiclePropertyIds; 36 import android.car.VehiclePropertyType; 37 import android.car.feature.Flags; 38 import android.car.hardware.CarHvacFanDirection; 39 import android.car.hardware.CarPropertyConfig; 40 import android.car.hardware.CarPropertyValue; 41 import android.car.hardware.property.AreaIdConfig; 42 import android.car.hardware.property.CarInternalErrorException; 43 import android.car.hardware.property.CarPropertyManager; 44 import android.car.hardware.property.CarPropertyManager.GetPropertyCallback; 45 import android.car.hardware.property.CarPropertyManager.GetPropertyRequest; 46 import android.car.hardware.property.CarPropertyManager.GetPropertyResult; 47 import android.car.hardware.property.CarPropertyManager.PropertyAsyncError; 48 import android.car.hardware.property.CarPropertyManager.SetPropertyCallback; 49 import android.car.hardware.property.CarPropertyManager.SetPropertyRequest; 50 import android.car.hardware.property.CarPropertyManager.SetPropertyResult; 51 import android.car.hardware.property.ErrorState; 52 import android.car.hardware.property.PropertyNotAvailableAndRetryException; 53 import android.car.hardware.property.PropertyNotAvailableErrorCode; 54 import android.car.hardware.property.PropertyNotAvailableException; 55 import android.car.hardware.property.Subscription; 56 import android.content.Context; 57 import android.os.Build; 58 import android.os.SystemClock; 59 import android.util.ArrayMap; 60 import android.util.Log; 61 import android.util.SparseArray; 62 import android.util.SparseIntArray; 63 64 import androidx.annotation.Nullable; 65 import androidx.test.platform.app.InstrumentationRegistry; 66 67 import com.android.bedstead.nene.TestApis; 68 import com.android.bedstead.permissions.PermissionContext; 69 import com.android.internal.annotations.GuardedBy; 70 71 import com.google.common.collect.ImmutableList; 72 import com.google.common.collect.ImmutableSet; 73 import com.google.common.collect.Sets; 74 75 import org.hamcrest.Matchers; 76 import org.junit.AssumptionViolatedException; 77 78 import java.util.ArrayList; 79 import java.util.Arrays; 80 import java.util.Collection; 81 import java.util.Collections; 82 import java.util.List; 83 import java.util.Optional; 84 import java.util.concurrent.CountDownLatch; 85 import java.util.concurrent.TimeUnit; 86 import java.util.concurrent.atomic.AtomicBoolean; 87 import java.util.stream.Collectors; 88 import java.util.stream.IntStream; 89 90 /** 91 * A class for verifying the implementation for one VHAL property. 92 * 93 * @param T The type for the property. 94 */ 95 public class VehiclePropertyVerifier<T> { 96 private static final String STEP_VERIFY_READ_APIS_PREFIX = "verifyReadApis"; 97 private static final String STEP_VERIFY_WRITE_APIS_PREFIX = "verifyWriteApis"; 98 99 /** 100 * A step to verify {@link CarPropertyManager#getCarPropertyConfig} returns expected config. 101 */ 102 public static final String STEP_VERIFY_PROPERTY_CONFIG = "verifyPropertyConfig"; 103 /** 104 * A step to verify that expected exceptions are thrown when missing permission. 105 */ 106 public static final String STEP_VERIFY_PERMISSION_NOT_GRANTED_EXCEPTION = 107 "verifyPermissionNotGrantedException"; 108 /** 109 * A step to verify {@link CarPropertyManager#getProperty}, 110 * {@link CarPropertyManager#getIntProperty}, {@link CarPropertyManager#getBooleanProperty}, 111 * {@link CarPropertyManager#getFloatProperty}, {@link CarPropertyManager#getIntArrayProperty}. 112 */ 113 public static final String STEP_VERIFY_READ_APIS_GET_PROPERTY_SYNC = 114 STEP_VERIFY_READ_APIS_PREFIX + ".getProperty"; 115 /** 116 * A step to verify {@link CarPropertyManager#getPropertiesAsync}. 117 */ 118 public static final String STEP_VERIFY_READ_APIS_GET_PROPERTY_ASYNC = 119 STEP_VERIFY_READ_APIS_PREFIX + ".getPropertiesAsync"; 120 /** 121 * A step to verify {@link CarPropertyManager#subscribePropertyEvents}. 122 */ 123 public static final String STEP_VERIFY_READ_APIS_SUBSCRIBE = 124 STEP_VERIFY_READ_APIS_PREFIX + ".subscribePropertyEvents"; 125 /** 126 * A step to verify that for ADAS properties, if the feature is disabled, the property must 127 * report error state. 128 * 129 * This step is skipped for non-adas properties. 130 */ 131 public static final String STEP_VERIFY_READ_APIS_DISABLE_ADAS_FEATURE_VERIFY_STATE = 132 STEP_VERIFY_READ_APIS_PREFIX + ".disableAdasFeatureVerifyState"; 133 /** 134 * A step to verify {@link CarPropertyManager#setProperty}. 135 */ 136 public static final String STEP_VERIFY_WRITE_APIS_SET_PROPERTY_SYNC = 137 STEP_VERIFY_WRITE_APIS_PREFIX + ".setProperty"; 138 /** 139 * A step to verify {@link CarPropertyManager#setPropertiesAsync}. 140 */ 141 public static final String STEP_VERIFY_WRITE_APIS_SET_PROPERTY_ASYNC = 142 STEP_VERIFY_WRITE_APIS_PREFIX + ".setPropertiesAsync"; 143 /** 144 * A step to verify that for ADAS properties, if the feature is disabled, the property must 145 * report error state. 146 * 147 * This step is skipped for non-adas properties. 148 */ 149 public static final String STEP_VERIFY_WRITE_APIS_DISABLE_ADAS_FEATURE_VERIFY_STATE = 150 STEP_VERIFY_WRITE_APIS_PREFIX + ".disableAdasFeatureVerifyState"; 151 152 /** 153 * A step to verify that for HVAC power dependant properties, getting should be unavailable 154 * when HVAC power is off. 155 */ 156 public static final String STEP_VERIFY_READ_APIS_DISABLE_HVAC_GET_NOT_AVAILABLE = 157 STEP_VERIFY_READ_APIS_PREFIX + ".turnOffHvacPowerGetNotAvailable"; 158 159 /** 160 * A step to verify that for HVAC power dependant properties, setting should be unavailable 161 * when HVAC power is off. 162 */ 163 public static final String STEP_VERIFY_WRITE_APIS_DISABLE_HVAC_SET_NOT_AVAILABLE = 164 STEP_VERIFY_WRITE_APIS_PREFIX + ".turnOffHvacPowerSetNotAvailable"; 165 /** 166 * A step to that expected exceptions are thrown when missing permissions for read APIs. 167 */ 168 public static final String STEP_VERIFY_READ_APIS_WITHOUT_PERMISSION = 169 "verifyReadApisWithoutPermission"; 170 /** 171 * A step to that expected exceptions are thrown when missing permissions for write APIs. 172 */ 173 public static final String STEP_VERIFY_WRITE_APIS_WITHOUT_PERMISSION = 174 "verifyWriteApisWithoutPermission"; 175 176 private static final String TAG = VehiclePropertyVerifier.class.getSimpleName(); 177 private static final String CAR_PROPERTY_VALUE_SOURCE_GETTER = "Getter"; 178 private static final String CAR_PROPERTY_VALUE_SOURCE_CALLBACK = "Callback"; 179 private static final int GLOBAL_AREA_ID = 0; 180 private static final float FLOAT_INEQUALITY_THRESHOLD = 0.00001f; 181 private static final int VENDOR_ERROR_CODE_MINIMUM_VALUE = 0x0; 182 private static final int VENDOR_ERROR_CODE_MAXIMUM_VALUE = 0xffff; 183 private static final int SET_PROPERTY_CALLBACK_TIMEOUT_SEC = 5; 184 private static final ImmutableSet<Integer> WHEEL_AREAS = ImmutableSet.of( 185 VehicleAreaWheel.WHEEL_LEFT_FRONT, VehicleAreaWheel.WHEEL_LEFT_REAR, 186 VehicleAreaWheel.WHEEL_RIGHT_FRONT, VehicleAreaWheel.WHEEL_RIGHT_REAR); 187 private static final ImmutableSet<Integer> ALL_POSSIBLE_WHEEL_AREA_IDS = 188 generateAllPossibleAreaIds(WHEEL_AREAS); 189 private static final ImmutableSet<Integer> WINDOW_AREAS = ImmutableSet.of( 190 VehicleAreaWindow.WINDOW_FRONT_WINDSHIELD, VehicleAreaWindow.WINDOW_REAR_WINDSHIELD, 191 VehicleAreaWindow.WINDOW_ROW_1_LEFT, VehicleAreaWindow.WINDOW_ROW_1_RIGHT, 192 VehicleAreaWindow.WINDOW_ROW_2_LEFT, VehicleAreaWindow.WINDOW_ROW_2_RIGHT, 193 VehicleAreaWindow.WINDOW_ROW_3_LEFT, VehicleAreaWindow.WINDOW_ROW_3_RIGHT, 194 VehicleAreaWindow.WINDOW_ROOF_TOP_1, VehicleAreaWindow.WINDOW_ROOF_TOP_2); 195 private static final ImmutableSet<Integer> ALL_POSSIBLE_WINDOW_AREA_IDS = 196 generateAllPossibleAreaIds(WINDOW_AREAS); 197 private static final ImmutableSet<Integer> MIRROR_AREAS = ImmutableSet.of( 198 VehicleAreaMirror.MIRROR_DRIVER_LEFT, VehicleAreaMirror.MIRROR_DRIVER_RIGHT, 199 VehicleAreaMirror.MIRROR_DRIVER_CENTER); 200 private static final ImmutableSet<Integer> ALL_POSSIBLE_MIRROR_AREA_IDS = 201 generateAllPossibleAreaIds(MIRROR_AREAS); 202 private static final ImmutableSet<Integer> SEAT_AREAS = ImmutableSet.of( 203 VehicleAreaSeat.SEAT_ROW_1_LEFT, VehicleAreaSeat.SEAT_ROW_1_CENTER, 204 VehicleAreaSeat.SEAT_ROW_1_RIGHT, VehicleAreaSeat.SEAT_ROW_2_LEFT, 205 VehicleAreaSeat.SEAT_ROW_2_CENTER, VehicleAreaSeat.SEAT_ROW_2_RIGHT, 206 VehicleAreaSeat.SEAT_ROW_3_LEFT, VehicleAreaSeat.SEAT_ROW_3_CENTER, 207 VehicleAreaSeat.SEAT_ROW_3_RIGHT); 208 private static final ImmutableSet<Integer> ALL_POSSIBLE_SEAT_AREA_IDS = 209 generateAllPossibleAreaIds(SEAT_AREAS); 210 private static final ImmutableSet<Integer> DOOR_AREAS = ImmutableSet.of( 211 VehicleAreaDoor.DOOR_ROW_1_LEFT, VehicleAreaDoor.DOOR_ROW_1_RIGHT, 212 VehicleAreaDoor.DOOR_ROW_2_LEFT, VehicleAreaDoor.DOOR_ROW_2_RIGHT, 213 VehicleAreaDoor.DOOR_ROW_3_LEFT, VehicleAreaDoor.DOOR_ROW_3_RIGHT, 214 VehicleAreaDoor.DOOR_HOOD, VehicleAreaDoor.DOOR_REAR); 215 private static final ImmutableSet<Integer> ALL_POSSIBLE_DOOR_AREA_IDS = 216 generateAllPossibleAreaIds(DOOR_AREAS); 217 private static final ImmutableSet<Integer> PROPERTY_NOT_AVAILABLE_ERROR_CODES = 218 ImmutableSet.of( 219 PropertyNotAvailableErrorCode.NOT_AVAILABLE, 220 PropertyNotAvailableErrorCode.NOT_AVAILABLE_DISABLED, 221 PropertyNotAvailableErrorCode.NOT_AVAILABLE_SPEED_LOW, 222 PropertyNotAvailableErrorCode.NOT_AVAILABLE_SPEED_HIGH, 223 PropertyNotAvailableErrorCode.NOT_AVAILABLE_POOR_VISIBILITY, 224 PropertyNotAvailableErrorCode.NOT_AVAILABLE_SAFETY); 225 private static final boolean AREA_ID_CONFIG_ACCESS_FLAG = 226 isAtLeastV() && Flags.areaIdConfigAccess(); 227 private static final List<Integer> VALID_CAR_PROPERTY_VALUE_STATUSES = Arrays.asList( 228 CarPropertyValue.STATUS_AVAILABLE, CarPropertyValue.STATUS_UNAVAILABLE, 229 CarPropertyValue.STATUS_ERROR); 230 231 private static final CarPropertyValueCallback FAKE_CALLBACK = new CarPropertyValueCallback( 232 /* propertyName= */ "", new int[]{}, /* totalCarPropertyValuesPerAreaId= */ 0, 233 /* timeoutMillis= */ 0); 234 235 private static Class<?> sExceptionClassOnGet; 236 private static Class<?> sExceptionClassOnSet; 237 238 private static boolean sIsCarPropertyConfigsCached; 239 // A static cache to store all property configs. This will be reused across multiple test cases 240 // in order to save time. 241 private static SparseArray<CarPropertyConfig<?>> sCachedCarPropertyConfigs = 242 new SparseArray<>(); 243 244 private final Context mContext = 245 InstrumentationRegistry.getInstrumentation().getTargetContext(); 246 private final CarPropertyManager mCarPropertyManager; 247 private final int mPropertyId; 248 private final String mPropertyName; 249 private final int mAccess; 250 private final int mAreaType; 251 private final int mChangeMode; 252 private final Class<T> mPropertyType; 253 private final boolean mRequiredProperty; 254 private final Optional<ConfigArrayVerifier> mConfigArrayVerifier; 255 private final Optional<CarPropertyValueVerifier<T>> mCarPropertyValueVerifier; 256 private final Optional<AreaIdsVerifier> mAreaIdsVerifier; 257 private final Optional<CarPropertyConfigVerifier> mCarPropertyConfigVerifier; 258 private final Optional<Integer> mDependentOnPropertyId; 259 private final ImmutableSet<String> mDependentOnPropertyPermissions; 260 private final ImmutableSet<Integer> mPossibleConfigArrayValues; 261 private final boolean mEnumIsBitMap; 262 private final ImmutableSet<T> mAllPossibleEnumValues; 263 private final ImmutableSet<T> mAllPossibleUnwritableValues; 264 private final ImmutableSet<T> mAllPossibleUnavailableValues; 265 private final boolean mRequirePropertyValueToBeInConfigArray; 266 private final boolean mVerifySetterWithConfigArrayValues; 267 private final boolean mRequireMinMaxValues; 268 private final boolean mRequireMinValuesToBeZero; 269 private final boolean mRequireZeroToBeContainedInMinMaxRanges; 270 private final boolean mPossiblyDependentOnHvacPowerOn; 271 private final boolean mVerifyErrorStates; 272 private final ImmutableSet<String> mReadPermissions; 273 private final ImmutableList<ImmutableSet<String>> mWritePermissions; 274 private final VerifierContext mVerifierContext; 275 private final List<Integer> mStoredProperties = new ArrayList<>(); 276 277 private SparseArray<SparseArray<?>> mPropertyToAreaIdValues; 278 VehiclePropertyVerifier( CarPropertyManager carPropertyManager, int propertyId, int access, int areaType, int changeMode, Class<T> propertyType, boolean requiredProperty, Optional<ConfigArrayVerifier> configArrayVerifier, Optional<CarPropertyValueVerifier<T>> carPropertyValueVerifier, Optional<AreaIdsVerifier> areaIdsVerifier, Optional<CarPropertyConfigVerifier> carPropertyConfigVerifier, Optional<Integer> dependentPropertyId, ImmutableSet<String> dependentOnPropertyPermissions, ImmutableSet<Integer> possibleConfigArrayValues, boolean enumIsBitMap, ImmutableSet<T> allPossibleEnumValues, ImmutableSet<T> allPossibleUnwritableValues, ImmutableSet<T> allPossibleUnavailableValues, boolean requirePropertyValueToBeInConfigArray, boolean verifySetterWithConfigArrayValues, boolean requireMinMaxValues, boolean requireMinValuesToBeZero, boolean requireZeroToBeContainedInMinMaxRanges, boolean possiblyDependentOnHvacPowerOn, boolean verifyErrorStates, ImmutableSet<String> readPermissions, ImmutableList<ImmutableSet<String>> writePermissions)279 private VehiclePropertyVerifier( 280 CarPropertyManager carPropertyManager, 281 int propertyId, 282 int access, 283 int areaType, 284 int changeMode, 285 Class<T> propertyType, 286 boolean requiredProperty, 287 Optional<ConfigArrayVerifier> configArrayVerifier, 288 Optional<CarPropertyValueVerifier<T>> carPropertyValueVerifier, 289 Optional<AreaIdsVerifier> areaIdsVerifier, 290 Optional<CarPropertyConfigVerifier> carPropertyConfigVerifier, 291 Optional<Integer> dependentPropertyId, 292 ImmutableSet<String> dependentOnPropertyPermissions, 293 ImmutableSet<Integer> possibleConfigArrayValues, 294 boolean enumIsBitMap, 295 ImmutableSet<T> allPossibleEnumValues, 296 ImmutableSet<T> allPossibleUnwritableValues, 297 ImmutableSet<T> allPossibleUnavailableValues, 298 boolean requirePropertyValueToBeInConfigArray, 299 boolean verifySetterWithConfigArrayValues, 300 boolean requireMinMaxValues, 301 boolean requireMinValuesToBeZero, 302 boolean requireZeroToBeContainedInMinMaxRanges, 303 boolean possiblyDependentOnHvacPowerOn, 304 boolean verifyErrorStates, 305 ImmutableSet<String> readPermissions, 306 ImmutableList<ImmutableSet<String>> writePermissions) { 307 assertWithMessage("Must set car property manager").that(carPropertyManager).isNotNull(); 308 mCarPropertyManager = carPropertyManager; 309 mPropertyId = propertyId; 310 mPropertyName = VehiclePropertyIds.toString(propertyId); 311 mAccess = access; 312 mAreaType = areaType; 313 mChangeMode = changeMode; 314 mPropertyType = propertyType; 315 mRequiredProperty = requiredProperty; 316 mConfigArrayVerifier = configArrayVerifier; 317 mCarPropertyValueVerifier = carPropertyValueVerifier; 318 mAreaIdsVerifier = areaIdsVerifier; 319 mCarPropertyConfigVerifier = carPropertyConfigVerifier; 320 mDependentOnPropertyId = dependentPropertyId; 321 mDependentOnPropertyPermissions = dependentOnPropertyPermissions; 322 mPossibleConfigArrayValues = possibleConfigArrayValues; 323 mEnumIsBitMap = enumIsBitMap; 324 mAllPossibleEnumValues = allPossibleEnumValues; 325 mAllPossibleUnwritableValues = allPossibleUnwritableValues; 326 mAllPossibleUnavailableValues = allPossibleUnavailableValues; 327 mRequirePropertyValueToBeInConfigArray = requirePropertyValueToBeInConfigArray; 328 mVerifySetterWithConfigArrayValues = verifySetterWithConfigArrayValues; 329 mRequireMinMaxValues = requireMinMaxValues; 330 mRequireMinValuesToBeZero = requireMinValuesToBeZero; 331 mRequireZeroToBeContainedInMinMaxRanges = requireZeroToBeContainedInMinMaxRanges; 332 mPossiblyDependentOnHvacPowerOn = possiblyDependentOnHvacPowerOn; 333 mVerifyErrorStates = verifyErrorStates; 334 mReadPermissions = readPermissions; 335 mWritePermissions = writePermissions; 336 mPropertyToAreaIdValues = new SparseArray<>(); 337 mVerifierContext = new VerifierContext(carPropertyManager); 338 } 339 340 /** 341 * Gets a new builder for the verifier. 342 */ newBuilder( int propertyId, int access, int areaType, int changeMode, Class<T> propertyType)343 public static <T> Builder<T> newBuilder( 344 int propertyId, int access, int areaType, int changeMode, Class<T> propertyType) { 345 return new Builder<>(propertyId, access, areaType, changeMode, propertyType); 346 } 347 348 /** 349 * Gets the ID of the property. 350 */ getPropertyId()351 public int getPropertyId() { 352 return mPropertyId; 353 } 354 355 /** 356 * Gets the name for the property. 357 */ getPropertyName()358 public String getPropertyName() { 359 return mPropertyName; 360 } 361 362 /** 363 * Gets the default value based on the type. 364 */ 365 @Nullable getDefaultValue(Class<?> clazz)366 public static <U> U getDefaultValue(Class<?> clazz) { 367 if (clazz == Boolean.class) { 368 return (U) Boolean.TRUE; 369 } 370 if (clazz == Integer.class) { 371 return (U) (Integer) 2; 372 } 373 if (clazz == Float.class) { 374 return (U) (Float) 2.f; 375 } 376 if (clazz == Long.class) { 377 return (U) (Long) 2L; 378 } 379 if (clazz == Integer[].class) { 380 return (U) new Integer[]{2}; 381 } 382 if (clazz == Float[].class) { 383 return (U) new Float[]{2.f}; 384 } 385 if (clazz == Long[].class) { 386 return (U) new Long[]{2L}; 387 } 388 if (clazz == String.class) { 389 return (U) new String("test"); 390 } 391 if (clazz == byte[].class) { 392 return (U) new byte[]{(byte) 0xbe, (byte) 0xef}; 393 } 394 return null; 395 } 396 accessToString(int access)397 private static String accessToString(int access) { 398 switch (access) { 399 case CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_NONE: 400 return "VEHICLE_PROPERTY_ACCESS_NONE"; 401 case CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ: 402 return "VEHICLE_PROPERTY_ACCESS_READ"; 403 case CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE: 404 return "VEHICLE_PROPERTY_ACCESS_WRITE"; 405 case CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE: 406 return "VEHICLE_PROPERTY_ACCESS_READ_WRITE"; 407 default: 408 return Integer.toString(access); 409 } 410 } 411 areaTypeToString(int areaType)412 private static String areaTypeToString(int areaType) { 413 switch (areaType) { 414 case VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL: 415 return "VEHICLE_AREA_TYPE_GLOBAL"; 416 case VehicleAreaType.VEHICLE_AREA_TYPE_WINDOW: 417 return "VEHICLE_AREA_TYPE_WINDOW"; 418 case VehicleAreaType.VEHICLE_AREA_TYPE_DOOR: 419 return "VEHICLE_AREA_TYPE_DOOR"; 420 case VehicleAreaType.VEHICLE_AREA_TYPE_MIRROR: 421 return "VEHICLE_AREA_TYPE_MIRROR"; 422 case VehicleAreaType.VEHICLE_AREA_TYPE_SEAT: 423 return "VEHICLE_AREA_TYPE_SEAT"; 424 case VehicleAreaType.VEHICLE_AREA_TYPE_WHEEL: 425 return "VEHICLE_AREA_TYPE_WHEEL"; 426 case VehicleAreaType.VEHICLE_AREA_TYPE_VENDOR: 427 return "VEHICLE_AREA_TYPE_VENDOR"; 428 default: 429 return Integer.toString(areaType); 430 } 431 } 432 changeModeToString(int changeMode)433 private static String changeModeToString(int changeMode) { 434 switch (changeMode) { 435 case CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_STATIC: 436 return "VEHICLE_PROPERTY_CHANGE_MODE_STATIC"; 437 case CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE: 438 return "VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE"; 439 case CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS: 440 return "VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS"; 441 default: 442 return Integer.toString(changeMode); 443 } 444 } 445 446 /** 447 * Gets the car property config for the current property or reads from cache if already cached. 448 * 449 * Note that we statically cache all the property configs using the shell permission. 450 */ getCarPropertyConfig()451 public @Nullable CarPropertyConfig<T> getCarPropertyConfig() { 452 return getCarPropertyConfig(/* useCache= */ true); 453 } 454 455 /** 456 * Gets the car property config for the current property. 457 * 458 * @param useCache Whether to use a local cache that prefetched all the configs using shell 459 * permission. 460 */ getCarPropertyConfig(boolean useCache)461 public @Nullable CarPropertyConfig<T> getCarPropertyConfig(boolean useCache) { 462 return (CarPropertyConfig<T>) getCarPropertyConfig(mPropertyId, useCache); 463 } 464 getCarPropertyConfig(int propertyId)465 private @Nullable CarPropertyConfig<?> getCarPropertyConfig(int propertyId) { 466 return getCarPropertyConfig(propertyId, /* useCache= */ true); 467 } 468 getCarPropertyConfig(int propertyId, boolean useCache)469 private @Nullable CarPropertyConfig<?> getCarPropertyConfig(int propertyId, boolean useCache) { 470 if (!useCache) { 471 return mCarPropertyManager.getCarPropertyConfig(propertyId); 472 } 473 474 if (!sIsCarPropertyConfigsCached) { 475 try (PermissionContext p = TestApis.permissions().withPermission( 476 TestApis.permissions().adoptablePermissions().toArray(new String[0]))) { 477 var configs = mCarPropertyManager.getPropertyList(); 478 for (int i = 0; i < configs.size(); i++) { 479 sCachedCarPropertyConfigs.put(configs.get(i).getPropertyId(), configs.get(i)); 480 } 481 } 482 sIsCarPropertyConfigsCached = true; 483 } 484 return sCachedCarPropertyConfigs.get(propertyId); 485 } 486 487 /** 488 * Returns whether the property is supported. 489 */ isSupported()490 public boolean isSupported() { 491 return getCarPropertyConfig() != null; 492 } 493 494 /** 495 * Gets all verification steps. 496 */ getAllSteps()497 public static ImmutableList<String> getAllSteps() { 498 return ImmutableList.of(STEP_VERIFY_PROPERTY_CONFIG, 499 STEP_VERIFY_PERMISSION_NOT_GRANTED_EXCEPTION, 500 STEP_VERIFY_READ_APIS_GET_PROPERTY_SYNC, 501 STEP_VERIFY_READ_APIS_GET_PROPERTY_ASYNC, 502 STEP_VERIFY_READ_APIS_SUBSCRIBE, 503 STEP_VERIFY_READ_APIS_DISABLE_ADAS_FEATURE_VERIFY_STATE, 504 STEP_VERIFY_WRITE_APIS_SET_PROPERTY_SYNC, 505 STEP_VERIFY_WRITE_APIS_SET_PROPERTY_ASYNC, 506 STEP_VERIFY_WRITE_APIS_DISABLE_ADAS_FEATURE_VERIFY_STATE, 507 STEP_VERIFY_READ_APIS_DISABLE_HVAC_GET_NOT_AVAILABLE, 508 STEP_VERIFY_WRITE_APIS_DISABLE_HVAC_SET_NOT_AVAILABLE, 509 STEP_VERIFY_READ_APIS_WITHOUT_PERMISSION, 510 STEP_VERIFY_WRITE_APIS_WITHOUT_PERMISSION 511 ); 512 } 513 514 /** 515 * Runs various verifications on the property. 516 */ verify()517 public void verify() { 518 verify(null); 519 } 520 521 /** 522 * Runs a specific verification step. 523 */ verify(String step, @Nullable Class<?> exceptedExceptionClass)524 public void verify(String step, @Nullable Class<?> exceptedExceptionClass) { 525 if (step.equals(STEP_VERIFY_PROPERTY_CONFIG)) { 526 verifyConfig(); 527 return; 528 } 529 assumeTrue("Property: " + getPropertyName() + " is not supported", isSupported()); 530 if (step.equals(STEP_VERIFY_PERMISSION_NOT_GRANTED_EXCEPTION)) { 531 verifyPermissionNotGrantedException(); 532 } else if (step.startsWith(STEP_VERIFY_READ_APIS_PREFIX)) { 533 verifyReadApis(step, exceptedExceptionClass); 534 } else if (step.startsWith(STEP_VERIFY_WRITE_APIS_PREFIX)) { 535 verifyWriteApis(step, exceptedExceptionClass); 536 } else if (step.equals(STEP_VERIFY_READ_APIS_WITHOUT_PERMISSION)) { 537 verifyReadApisWithoutPermission(); 538 } else if (step.equals(STEP_VERIFY_WRITE_APIS_WITHOUT_PERMISSION)) { 539 verifyWriteApisWithoutPermission(); 540 } else { 541 throw new IllegalStateException("Unknown step: " + step); 542 } 543 } 544 545 /** 546 * Runs all verification steps on the property with exceptions expected. 547 * 548 * @param exceptedExceptionClass The exception class expected for reading/writing the property. 549 */ verify(@ullable Class<?> exceptedExceptionClass)550 public void verify(@Nullable Class<?> exceptedExceptionClass) { 551 verifySteps(getAllSteps(), exceptedExceptionClass); 552 } 553 verifySteps(List<String> steps, @Nullable Class<?> exceptedExceptionClass)554 private void verifySteps(List<String> steps, @Nullable Class<?> exceptedExceptionClass) { 555 for (int i = 0; i < steps.size(); i++) { 556 try { 557 verify(steps.get(i), exceptedExceptionClass); 558 } catch (AssumptionViolatedException e) { 559 if (steps.get(i).equals(STEP_VERIFY_PROPERTY_CONFIG)) { 560 // If the assumption fails for verifying config. It means the property is not 561 // supported and we should not continue the rest of the steps. 562 throw e; 563 } else { 564 // Otherwise, we allow one step to be skipped. 565 continue; 566 } 567 } 568 } 569 } 570 assertGetPropertyNotSupported(String msg)571 private void assertGetPropertyNotSupported(String msg) { 572 if (isAtLeastU()) { 573 assertThrows(msg, IllegalArgumentException.class, 574 () -> mCarPropertyManager.getProperty(mPropertyId, /*areaId=*/ 0)); 575 } else { 576 assertThat(mCarPropertyManager.getProperty(mPropertyId, /* areaId= */ 0)).isNull(); 577 } 578 } 579 580 /** 581 * Verifies the configuration for the property. 582 */ verifyConfig()583 public void verifyConfig() { 584 ImmutableSet.Builder<String> permissionsBuilder = ImmutableSet.<String>builder(); 585 for (ImmutableSet<String> writePermissions: mWritePermissions) { 586 permissionsBuilder.addAll(writePermissions); 587 } 588 ImmutableSet<String> allPermissions = permissionsBuilder.addAll(mReadPermissions).build(); 589 590 runWithShellPermissionIdentity( 591 () -> { 592 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig( 593 /* useCache= */ false); 594 if (carPropertyConfig == null) { 595 if (mAccess == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ || mAccess 596 == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE) { 597 assertGetPropertyNotSupported( 598 "Test does not have correct permissions granted for " 599 + mPropertyName + ". Requested permissions: " + allPermissions); 600 } else if (mAccess == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE) { 601 assertThrows("Test does not have correct permissions granted for " 602 + mPropertyName + ". Requested permissions: " 603 + allPermissions, 604 IllegalArgumentException.class, 605 () -> mCarPropertyManager.setProperty(mPropertyType, 606 mPropertyId, /*areaId=*/ 607 0, getDefaultValue(mPropertyType))); 608 } 609 } 610 611 if (mRequiredProperty) { 612 assertWithMessage("Must support " + mPropertyName).that(isSupported()) 613 .isTrue(); 614 } else { 615 assumeThat("Skipping " + mPropertyName 616 + " CTS test because the property is not supported on " 617 + "this vehicle", 618 carPropertyConfig, Matchers.notNullValue()); 619 } 620 621 verifyCarPropertyConfig(); 622 }, allPermissions.toArray(new String[0])); 623 } 624 625 /** 626 * Verifies that caller can call read APIs with read permission. 627 */ verifyReadApis(String step, Class<?> exceptedExceptionClass)628 private void verifyReadApis(String step, Class<?> exceptedExceptionClass) { 629 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig(); 630 for (String readPermission: mReadPermissions) { 631 verifyReadPermissionGivesAccessToReadApis(step, readPermission, exceptedExceptionClass); 632 } 633 } 634 635 /** 636 * Verifies that caller can call write APIs with write permission. 637 */ verifyWriteApis(String step, Class<?> exceptedExceptionClass)638 private void verifyWriteApis(String step, Class<?> exceptedExceptionClass) { 639 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig(); 640 for (ImmutableSet<String> writePermissions: mWritePermissions) { 641 verifyWritePermissionsGiveAccessToWriteApis(step, writePermissions, mReadPermissions, 642 exceptedExceptionClass); 643 } 644 } 645 646 /** 647 * Verifies that caller cannot call read APIs without read permission. 648 */ verifyReadApisWithoutPermission()649 private void verifyReadApisWithoutPermission() { 650 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig(); 651 for (String readPermission: mReadPermissions) { 652 if (AREA_ID_CONFIG_ACCESS_FLAG) { 653 for (int areaId : carPropertyConfig.getAreaIds()) { 654 if (carPropertyConfig.getAreaIdConfig(areaId).getAccess() 655 == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE) { 656 verifyReadPermissionCannotWrite(readPermission, mWritePermissions, areaId); 657 } 658 } 659 } else if (carPropertyConfig.getAccess() 660 == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE) { 661 verifyReadPermissionCannotWrite(readPermission, mWritePermissions, 662 carPropertyConfig.getAreaIds()[0]); 663 } 664 } 665 } 666 667 /** 668 * Verifies that caller cannot call write APIs without write permission. 669 */ verifyWriteApisWithoutPermission()670 private void verifyWriteApisWithoutPermission() { 671 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig(); 672 for (ImmutableSet<String> writePermissions: mWritePermissions) { 673 if (AREA_ID_CONFIG_ACCESS_FLAG) { 674 for (int areaId : carPropertyConfig.getAreaIds()) { 675 int access = carPropertyConfig.getAreaIdConfig(areaId).getAccess(); 676 if (access != CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE) { 677 verifyWritePermissionsCannotRead(writePermissions, mReadPermissions, 678 areaId); 679 } 680 if (access != CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ 681 && writePermissions.size() > 1) { 682 verifyIndividualWritePermissionsCannotWrite(writePermissions, areaId); 683 } 684 } 685 } else { 686 int areaId = carPropertyConfig.getAreaIds()[0]; 687 if (carPropertyConfig.getAccess() 688 != CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE) { 689 verifyWritePermissionsCannotRead(writePermissions, mReadPermissions, areaId); 690 } 691 if (carPropertyConfig.getAccess() 692 == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ) { 693 return; 694 } 695 if (writePermissions.size() > 1) { 696 verifyIndividualWritePermissionsCannotWrite(writePermissions, areaId); 697 } 698 } 699 } 700 } 701 hasWritePermissions(ImmutableList<ImmutableSet<String>> writePermissions)702 private boolean hasWritePermissions(ImmutableList<ImmutableSet<String>> writePermissions) { 703 for (ImmutableSet<String> writePermissionSet: writePermissions) { 704 boolean result = true; 705 for (String permission : writePermissionSet) { 706 if (mContext.checkSelfPermission(permission) != PERMISSION_GRANTED) { 707 result = false; 708 break; 709 } 710 } 711 if (result) { 712 return true; 713 } 714 } 715 return false; 716 } 717 verifyReadPermissionCannotWrite(String readPermission, ImmutableList<ImmutableSet<String>> writePermissions, int areaId)718 private void verifyReadPermissionCannotWrite(String readPermission, 719 ImmutableList<ImmutableSet<String>> writePermissions, int areaId) { 720 // If the read permission is the same as the write permission and the property does not 721 // require any other write permissions we skip this permission. 722 for (ImmutableSet<String> writePermissionSet: writePermissions) { 723 if (writePermissionSet.size() == 1 && writePermissionSet.contains(readPermission)) { 724 return; 725 } 726 } 727 // It is possible that the caller has the write permissions without adopting the shell 728 // identity. In this case, we cannot revoke the write permission so we cannot test 729 // setProperty without write permissions. 730 if (hasWritePermissions(writePermissions)) { 731 return; 732 } 733 runWithShellPermissionIdentity( 734 () -> { 735 assertThrows( 736 mPropertyName 737 + " - property ID: " 738 + mPropertyId 739 + " should not be able to be written to without write" 740 + " permissions.", 741 SecurityException.class, 742 () -> mCarPropertyManager.setProperty(mPropertyType, mPropertyId, 743 areaId, getDefaultValue(mPropertyType))); 744 }, readPermission); 745 } 746 verifyReadPermissionGivesAccessToReadApis(String step, String readPermission, Class<?> exceptedExceptionClass)747 private void verifyReadPermissionGivesAccessToReadApis(String step, String readPermission, 748 Class<?> exceptedExceptionClass) { 749 if (step.equals(STEP_VERIFY_READ_APIS_DISABLE_ADAS_FEATURE_VERIFY_STATE)) { 750 assumeTrue("Not an ADAS property", mDependentOnPropertyId.isPresent()); 751 disableAdasFeatureIfAdasStatePropertyAndVerify(ImmutableSet.<String>builder() 752 .add(readPermission) 753 .addAll(mDependentOnPropertyPermissions) 754 .build().toArray(new String[0]), /* verifySet= */ false); 755 return; 756 } 757 758 try { 759 enableAdasFeatureIfAdasStateProperty(); 760 runWithShellPermissionIdentity(() -> { 761 assertThat(getCarPropertyConfig(/* useCache= */ false)).isNotNull(); 762 turnOnHvacPowerIfHvacPowerDependent(); 763 if (step.equals(STEP_VERIFY_READ_APIS_GET_PROPERTY_SYNC)) { 764 verifyCarPropertyValueGetter(); 765 if (exceptedExceptionClass != null) { 766 assertWithMessage("Expected " + sExceptionClassOnGet + " to be of type " 767 + exceptedExceptionClass).that(sExceptionClassOnGet) 768 .isEqualTo(exceptedExceptionClass); 769 } 770 return; 771 } 772 if (exceptedExceptionClass != null) { 773 return; 774 } 775 776 if (step.equals(STEP_VERIFY_READ_APIS_GET_PROPERTY_ASYNC)) { 777 verifyGetPropertiesAsync(); 778 } 779 780 if (step.equals(STEP_VERIFY_READ_APIS_SUBSCRIBE)) { 781 verifyCarPropertyValueCallback(); 782 } 783 784 if (step.equals(STEP_VERIFY_READ_APIS_DISABLE_HVAC_GET_NOT_AVAILABLE)) { 785 assumeTrue("Not depending on HVAC power", mPossiblyDependentOnHvacPowerOn); 786 if (turnOffHvacPowerIfHvacPowerDependent()) { 787 verifyGetNotAvailable(); 788 } 789 } 790 }, readPermission); 791 } finally { 792 // Restore all property values even if test fails. 793 runWithShellPermissionIdentity(() -> { 794 restoreInitialValues(); 795 }, ImmutableSet.<String>builder() 796 .add(readPermission) 797 .addAll(mDependentOnPropertyPermissions) 798 .build().toArray(new String[0])); 799 } 800 } 801 hasReadPermissions(ImmutableSet<String> allReadPermissions)802 private boolean hasReadPermissions(ImmutableSet<String> allReadPermissions) { 803 for (String permission : allReadPermissions) { 804 if (mContext.checkSelfPermission(permission) == PERMISSION_GRANTED) { 805 return true; 806 } 807 } 808 return false; 809 } 810 assertGetPropertyThrowsException(String msg, Class<? extends Throwable> exceptionClass, int propertyId, int areaId)811 private void assertGetPropertyThrowsException(String msg, 812 Class<? extends Throwable> exceptionClass, int propertyId, int areaId) { 813 assertThrows(msg, exceptionClass, 814 () -> mCarPropertyManager.getProperty(mPropertyId, areaId)); 815 assertThrows(msg, exceptionClass, 816 () -> mCarPropertyManager.getBooleanProperty(mPropertyId, areaId)); 817 assertThrows(msg, exceptionClass, 818 () -> mCarPropertyManager.getIntProperty(mPropertyId, areaId)); 819 assertThrows(msg, exceptionClass, 820 () -> mCarPropertyManager.getFloatProperty(mPropertyId, areaId)); 821 assertThrows(msg, exceptionClass, 822 () -> mCarPropertyManager.getIntArrayProperty(mPropertyId, areaId)); 823 } 824 verifyWritePermissionsCannotRead(ImmutableSet<String> writePermissions, ImmutableSet<String> allReadPermissions, int areaId)825 private void verifyWritePermissionsCannotRead(ImmutableSet<String> writePermissions, 826 ImmutableSet<String> allReadPermissions, int areaId) { 827 // If there is any write permission that is also a read permission we skip the permissions. 828 if (!Collections.disjoint(writePermissions, allReadPermissions)) { 829 return; 830 } 831 // It is possible that the caller has the read permissions without adopting the shell 832 // identity. In this case, we cannot revoke the read permissions so we cannot test 833 // getProperty without read permissions. 834 if (hasReadPermissions(allReadPermissions)) { 835 return; 836 } 837 runWithShellPermissionIdentity( 838 () -> { 839 assertGetPropertyThrowsException( 840 mPropertyName 841 + " - property ID: " 842 + mPropertyId 843 + " should not be able to be read without read" 844 + " permissions.", 845 SecurityException.class, mPropertyId, areaId); 846 assertThrows( 847 mPropertyName 848 + " - property ID: " 849 + mPropertyId 850 + " should not be able to be listened to without read" 851 + " permissions.", 852 SecurityException.class, 853 () -> verifyCarPropertyValueCallback()); 854 assertThrows( 855 mPropertyName 856 + " - property ID: " 857 + mPropertyId 858 + " should not be able to be read without read" 859 + " permissions.", 860 SecurityException.class, 861 () -> verifyGetPropertiesAsync()); 862 863 // If the caller only has write permission, registerCallback throws 864 // SecurityException. 865 assertThrows( 866 mPropertyName 867 + " - property ID: " 868 + mPropertyId 869 + " should not be able to be listened to without read" 870 + " permission.", 871 SecurityException.class, 872 () -> mCarPropertyManager.registerCallback( 873 FAKE_CALLBACK, mPropertyId, 0f)); 874 875 if (isAtLeastV() && Flags.variableUpdateRate()) { 876 // For the new API, if the caller does not read permission, it throws 877 // SecurityException. 878 assertThrows( 879 mPropertyName 880 + " - property ID: " 881 + mPropertyId 882 + " should not be able to be listened to without read" 883 + " permission.", 884 SecurityException.class, 885 () -> mCarPropertyManager.subscribePropertyEvents(mPropertyId, 886 areaId, FAKE_CALLBACK)); 887 } 888 }, writePermissions.toArray(new String[0])); 889 } 890 verifyIndividualWritePermissionsCannotWrite( ImmutableSet<String> writePermissions, int areaId)891 private void verifyIndividualWritePermissionsCannotWrite( 892 ImmutableSet<String> writePermissions, int areaId) { 893 // It is possible that the caller has the write permissions without adopting 894 // the shell identity. In this case, we cannot revoke individual permissions. 895 if (hasWritePermissions(ImmutableList.of(writePermissions))) { 896 return; 897 } 898 899 String writePermissionsNeededString = String.join(", ", writePermissions); 900 for (String writePermission: writePermissions) { 901 runWithShellPermissionIdentity( 902 () -> { 903 assertThat(getCarPropertyConfig(/* useCache= */ false)).isNull(); 904 assertThrows( 905 mPropertyName 906 + " - property ID: " 907 + mPropertyId 908 + " should not be able to be written to without all of the" 909 + " following permissions granted: " 910 + writePermissionsNeededString, 911 SecurityException.class, 912 () -> mCarPropertyManager.setProperty(mPropertyType, mPropertyId, 913 areaId, getDefaultValue(mPropertyType))); 914 }, writePermission); 915 } 916 } 917 verifyWritePermissionsGiveAccessToWriteApis(String step, ImmutableSet<String> writePermissions, ImmutableSet<String> readPermissions, Class<?> exceptedExceptionClass)918 private void verifyWritePermissionsGiveAccessToWriteApis(String step, 919 ImmutableSet<String> writePermissions, ImmutableSet<String> readPermissions, 920 Class<?> exceptedExceptionClass) { 921 ImmutableSet<String> propertyPermissions = 922 ImmutableSet.<String>builder() 923 .addAll(writePermissions) 924 .addAll(readPermissions) 925 .build(); 926 927 if (step.equals(STEP_VERIFY_WRITE_APIS_DISABLE_ADAS_FEATURE_VERIFY_STATE)) { 928 assumeTrue("Not an ADAS property", mDependentOnPropertyId.isPresent()); 929 disableAdasFeatureIfAdasStatePropertyAndVerify( 930 propertyPermissions.toArray(new String[0]), /* verifySet= */ true); 931 return; 932 } 933 934 try { 935 // Store the current value before we call enableAdasFeatureIfAdasStateProperty, which 936 // might change this. 937 runWithShellPermissionIdentity(() -> { 938 storeCurrentValues(); 939 }, propertyPermissions.toArray(new String[0])); 940 enableAdasFeatureIfAdasStateProperty(); 941 942 runWithShellPermissionIdentity(() -> { 943 turnOnHvacPowerIfHvacPowerDependent(); 944 945 if (step.equals(STEP_VERIFY_WRITE_APIS_SET_PROPERTY_SYNC)) { 946 verifyCarPropertyValueSetter(); 947 if (exceptedExceptionClass != null) { 948 assertWithMessage("Expected " + sExceptionClassOnSet + " to be of type " 949 + exceptedExceptionClass).that(sExceptionClassOnSet) 950 .isEqualTo(exceptedExceptionClass); 951 } 952 } 953 954 if (step.equals(STEP_VERIFY_WRITE_APIS_SET_PROPERTY_ASYNC) 955 && exceptedExceptionClass == null) { 956 verifySetPropertiesAsync(); 957 } 958 if (step.equals(STEP_VERIFY_WRITE_APIS_DISABLE_HVAC_SET_NOT_AVAILABLE)) { 959 assumeTrue("Not depending on HVAC power", mPossiblyDependentOnHvacPowerOn); 960 if (turnOffHvacPowerIfHvacPowerDependent()) { 961 verifySetNotAvailable(); 962 } 963 } 964 }, propertyPermissions.toArray(new String[0])); 965 } finally { 966 // Restore all property values even if test fails. 967 runWithShellPermissionIdentity(() -> { 968 restoreInitialValues(); 969 }, ImmutableSet.<String>builder() 970 .addAll(propertyPermissions) 971 .addAll(mDependentOnPropertyPermissions) 972 .build().toArray(new String[0])); 973 } 974 } 975 turnOnHvacPowerIfHvacPowerDependent()976 private void turnOnHvacPowerIfHvacPowerDependent() { 977 if (!mPossiblyDependentOnHvacPowerOn) { 978 return; 979 } 980 981 CarPropertyConfig<Boolean> hvacPowerOnCarPropertyConfig = (CarPropertyConfig<Boolean>) 982 getCarPropertyConfig(VehiclePropertyIds.HVAC_POWER_ON); 983 if (hvacPowerOnCarPropertyConfig == null 984 || !hvacPowerOnCarPropertyConfig.getConfigArray().contains(mPropertyId)) { 985 return; 986 } 987 988 storeCurrentValuesForProperty(hvacPowerOnCarPropertyConfig); 989 // Turn the power on for all supported HVAC area IDs. 990 setBooleanPropertyInAllAreaIds(hvacPowerOnCarPropertyConfig, /* setValue: */ Boolean.TRUE); 991 } 992 turnOffHvacPowerIfHvacPowerDependent()993 private boolean turnOffHvacPowerIfHvacPowerDependent() { 994 CarPropertyConfig<Boolean> hvacPowerOnCarPropertyConfig = (CarPropertyConfig<Boolean>) 995 getCarPropertyConfig(VehiclePropertyIds.HVAC_POWER_ON); 996 if (hvacPowerOnCarPropertyConfig == null 997 || !hvacPowerOnCarPropertyConfig.getConfigArray().contains(mPropertyId)) { 998 return false; 999 } 1000 1001 // Turn the power off for all supported HVAC area IDs. 1002 setBooleanPropertyInAllAreaIds(hvacPowerOnCarPropertyConfig, /* setValue: */ Boolean.FALSE); 1003 return true; 1004 } 1005 1006 /** 1007 * Enables the ADAS feature if the property is an ADAS property. 1008 */ enableAdasFeatureIfAdasStateProperty()1009 public void enableAdasFeatureIfAdasStateProperty() { 1010 if (!mDependentOnPropertyId.isPresent()) { 1011 return; 1012 } 1013 1014 runWithShellPermissionIdentity(() -> { 1015 int adasEnabledPropertyId = mDependentOnPropertyId.get(); 1016 CarPropertyConfig<Boolean> adasEnabledCarPropertyConfig = (CarPropertyConfig<Boolean>) 1017 getCarPropertyConfig(adasEnabledPropertyId); 1018 1019 if (adasEnabledCarPropertyConfig == null || getAreaIdAccessOrElseGlobalAccess( 1020 adasEnabledCarPropertyConfig, GLOBAL_AREA_ID) 1021 == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ) { 1022 Log.w(TAG, "Cannot enable " + VehiclePropertyIds.toString(adasEnabledPropertyId) 1023 + " for testing " + VehiclePropertyIds.toString(mPropertyId) 1024 + " because property is either not implemented or READ only." 1025 + " Manually enable if it's not already enabled."); 1026 return; 1027 } 1028 1029 storeCurrentValuesForProperty(adasEnabledCarPropertyConfig); 1030 // Enable ADAS feature in all supported area IDs. 1031 setBooleanPropertyInAllAreaIds(adasEnabledCarPropertyConfig, 1032 /* setValue: */ Boolean.TRUE); 1033 }, mDependentOnPropertyPermissions.toArray(new String[0])); 1034 } 1035 disableAdasFeatureIfAdasStatePropertyAndVerify( String[] enabledPermissionsList, boolean verifySet)1036 private void disableAdasFeatureIfAdasStatePropertyAndVerify( 1037 String[] enabledPermissionsList, boolean verifySet) { 1038 try { 1039 if (disableAdasFeatureIfAdasStateProperty()) { 1040 runWithShellPermissionIdentity(() -> { 1041 verifyAdasPropertyDisabled(verifySet); 1042 }, enabledPermissionsList); 1043 } 1044 } finally { 1045 // Restore all property values even if test fails. 1046 runWithShellPermissionIdentity(() -> { 1047 restoreInitialValues(); 1048 }, mDependentOnPropertyPermissions.toArray(new String[0])); 1049 } 1050 } 1051 1052 /** 1053 * Disables the ADAS feature if the property is an ADAS property. 1054 */ disableAdasFeatureIfAdasStateProperty()1055 public boolean disableAdasFeatureIfAdasStateProperty() { 1056 if (!mDependentOnPropertyId.isPresent()) { 1057 return false; 1058 } 1059 1060 AtomicBoolean isDisabled = new AtomicBoolean(false); 1061 runWithShellPermissionIdentity(() -> { 1062 int adasEnabledPropertyId = mDependentOnPropertyId.get(); 1063 CarPropertyConfig<Boolean> adasEnabledCarPropertyConfig = (CarPropertyConfig<Boolean>) 1064 getCarPropertyConfig(adasEnabledPropertyId); 1065 1066 if (adasEnabledCarPropertyConfig == null || getAreaIdAccessOrElseGlobalAccess( 1067 adasEnabledCarPropertyConfig, GLOBAL_AREA_ID) 1068 == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ) { 1069 return; 1070 } 1071 1072 storeCurrentValuesForProperty(adasEnabledCarPropertyConfig); 1073 1074 // Disable ADAS feature in all supported area IDs. 1075 setBooleanPropertyInAllAreaIds(adasEnabledCarPropertyConfig, 1076 /* setValue: */ Boolean.FALSE); 1077 isDisabled.set(true); 1078 }, mDependentOnPropertyPermissions.toArray(new String[0])); 1079 return isDisabled.get(); 1080 } 1081 1082 /** 1083 * Stores the property's current values for all areas so that they can be restored later. 1084 */ storeCurrentValues()1085 public void storeCurrentValues() { 1086 storeCurrentValuesForProperty(getCarPropertyConfig()); 1087 } 1088 storeCurrentValuesForProperty(CarPropertyConfig<U> carPropertyConfig)1089 private <U> void storeCurrentValuesForProperty(CarPropertyConfig<U> carPropertyConfig) { 1090 SparseArray<U> areaIdToInitialValue = 1091 getInitialValuesByAreaId(carPropertyConfig, mCarPropertyManager); 1092 if (areaIdToInitialValue == null || areaIdToInitialValue.size() == 0) { 1093 return; 1094 } 1095 var propertyId = carPropertyConfig.getPropertyId(); 1096 if (mPropertyToAreaIdValues.contains(propertyId)) { 1097 throw new IllegalStateException("The property: " 1098 + VehiclePropertyIds.toString(propertyId) + " already has a stored value"); 1099 } 1100 mStoredProperties.add(propertyId); 1101 for (int i = 0; i < areaIdToInitialValue.size(); i++) { 1102 Log.i(TAG, "Storing initial value for property:" 1103 + VehiclePropertyIds.toString(propertyId) + " at area ID: " 1104 + areaIdToInitialValue.keyAt(i) 1105 + " to " + areaIdToInitialValue.valueAt(i)); 1106 } 1107 mPropertyToAreaIdValues.put(propertyId, areaIdToInitialValue); 1108 } 1109 restoreInitialValue(int propertyId)1110 private void restoreInitialValue(int propertyId) { 1111 var carPropertyConfig = getCarPropertyConfig(propertyId); 1112 SparseArray<?> areaIdToInitialValue = mPropertyToAreaIdValues.get(propertyId); 1113 1114 if (areaIdToInitialValue == null || carPropertyConfig == null) { 1115 Log.w(TAG, "No stored values for " + VehiclePropertyIds.toString(propertyId) 1116 + " to restore to, ignore"); 1117 return; 1118 } 1119 1120 restoreInitialValuesByAreaId(carPropertyConfig, mCarPropertyManager, 1121 areaIdToInitialValue); 1122 } 1123 1124 /** 1125 * Restore the property's and dependent properties values to original values stored by previous 1126 * {@link #storeCurrentValues}. 1127 * 1128 * Do nothing if no stored current values are available. 1129 * 1130 * The properties values are restored in the reverse-order as they are stored. 1131 */ restoreInitialValues()1132 public void restoreInitialValues() { 1133 for (int i = mStoredProperties.size() - 1; i >= 0; i--) { 1134 restoreInitialValue(mStoredProperties.get(i)); 1135 } 1136 mStoredProperties.clear(); 1137 mPropertyToAreaIdValues.clear(); 1138 } 1139 1140 // Get a map storing the property's area Ids to the initial values. 1141 @Nullable getInitialValuesByAreaId( CarPropertyConfig<U> carPropertyConfig, CarPropertyManager carPropertyManager)1142 private static <U> SparseArray<U> getInitialValuesByAreaId( 1143 CarPropertyConfig<U> carPropertyConfig, CarPropertyManager carPropertyManager) { 1144 if (!AREA_ID_CONFIG_ACCESS_FLAG && carPropertyConfig.getAccess() 1145 != CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE) { 1146 return null; 1147 } 1148 SparseArray<U> areaIdToInitialValue = new SparseArray<U>(); 1149 int propertyId = carPropertyConfig.getPropertyId(); 1150 String propertyName = VehiclePropertyIds.toString(propertyId); 1151 for (int areaId : carPropertyConfig.getAreaIds()) { 1152 if (doesAreaIdAccessNotMatch(carPropertyConfig, areaId, 1153 CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE)) { 1154 continue; 1155 } 1156 CarPropertyValue<U> carPropertyValue = null; 1157 try { 1158 carPropertyValue = carPropertyManager.getProperty(propertyId, areaId); 1159 } catch (PropertyNotAvailableAndRetryException | PropertyNotAvailableException 1160 | CarInternalErrorException e) { 1161 Log.w(TAG, "Failed to get property:" + propertyName + " at area ID: " + areaId 1162 + " to save initial car property value. Error: " + e); 1163 continue; 1164 } 1165 if (carPropertyValue == null) { 1166 Log.w(TAG, "Failed to get property:" + propertyName + " at area ID: " + areaId 1167 + " to save initial car property value."); 1168 continue; 1169 } 1170 areaIdToInitialValue.put(areaId, (U) carPropertyValue.getValue()); 1171 } 1172 return areaIdToInitialValue; 1173 } 1174 1175 /** 1176 * Set boolean property to a desired value in all supported area IDs. 1177 */ setBooleanPropertyInAllAreaIds(CarPropertyConfig<Boolean> booleanCarPropertyConfig, Boolean setValue)1178 private void setBooleanPropertyInAllAreaIds(CarPropertyConfig<Boolean> booleanCarPropertyConfig, 1179 Boolean setValue) { 1180 int propertyId = booleanCarPropertyConfig.getPropertyId(); 1181 for (int areaId : booleanCarPropertyConfig.getAreaIds()) { 1182 if (mCarPropertyManager.getBooleanProperty(propertyId, areaId) == setValue) { 1183 continue; 1184 } 1185 CarPropertyValue<Boolean> carPropertyValue = setPropertyAndWaitForChange( 1186 mCarPropertyManager, propertyId, Boolean.class, areaId, setValue); 1187 assertWithMessage( 1188 VehiclePropertyIds.toString(propertyId) 1189 + " carPropertyValue is null for area id: " + areaId) 1190 .that(carPropertyValue).isNotNull(); 1191 } 1192 } 1193 1194 // Restore the initial values of the property provided by {@code areaIdToInitialValue}. restoreInitialValuesByAreaId(CarPropertyConfig<?> carPropertyConfig, CarPropertyManager carPropertyManager, SparseArray<?> areaIdToInitialValue)1195 private static void restoreInitialValuesByAreaId(CarPropertyConfig<?> carPropertyConfig, 1196 CarPropertyManager carPropertyManager, SparseArray<?> areaIdToInitialValue) { 1197 int propertyId = carPropertyConfig.getPropertyId(); 1198 String propertyName = VehiclePropertyIds.toString(propertyId); 1199 for (int i = 0; i < areaIdToInitialValue.size(); i++) { 1200 int areaId = areaIdToInitialValue.keyAt(i); 1201 Object originalValue = areaIdToInitialValue.valueAt(i); 1202 CarPropertyValue<?> currentCarPropertyValue = null; 1203 try { 1204 currentCarPropertyValue = carPropertyManager.getProperty(propertyId, areaId); 1205 } catch (PropertyNotAvailableAndRetryException | PropertyNotAvailableException 1206 | CarInternalErrorException e) { 1207 Log.w(TAG, "Failed to get property:" + propertyName + " at area ID: " + areaId 1208 + " to restore initial car property value. Error: " + e); 1209 continue; 1210 } 1211 if (currentCarPropertyValue == null) { 1212 Log.w(TAG, "Failed to get property:" + propertyName + " at area ID: " + areaId 1213 + " to restore initial car property value."); 1214 continue; 1215 } 1216 Log.i(TAG, "Restoring value for: " + propertyName + " at area ID: " + areaId + " to " 1217 + originalValue); 1218 Object currentValue = currentCarPropertyValue.getValue(); 1219 if (valueEquals(originalValue, currentValue)) { 1220 continue; 1221 } 1222 CarPropertyValue<Object> carPropertyValue = setPropertyAndWaitForChange( 1223 carPropertyManager, propertyId, Object.class, areaId, originalValue); 1224 assertWithMessage( 1225 "Failed to restore car property value for property: " + propertyName 1226 + " at area ID: " + areaId + " to its original value: " + originalValue 1227 + ", current value: " + currentValue) 1228 .that(carPropertyValue).isNotNull(); 1229 } 1230 } 1231 1232 /** 1233 * Gets the possible values that could be set to. 1234 * 1235 * The values returned here must not cause {@code IllegalArgumentException} for set. 1236 * 1237 * Returns {@code null} or empty array if we don't know possible values. 1238 */ getPossibleValues(int areaId)1239 public @Nullable Collection<T> getPossibleValues(int areaId) { 1240 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig(); 1241 if (Boolean.class.equals(carPropertyConfig.getPropertyType())) { 1242 return (List<T>) List.of(Boolean.TRUE, Boolean.FALSE); 1243 } else if (Integer.class.equals(carPropertyConfig.getPropertyType())) { 1244 return (List<T>) getPossibleIntegerValues(areaId); 1245 } else if (Float.class.equals(carPropertyConfig.getPropertyType())) { 1246 return getPossibleFloatValues(areaId); 1247 } 1248 return null; 1249 } 1250 isAtLeastU()1251 public static boolean isAtLeastU() { 1252 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE; 1253 } 1254 isAtLeastV()1255 private static boolean isAtLeastV() { 1256 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM; 1257 } 1258 1259 /** 1260 * Gets the possible values for an integer property. 1261 */ getPossibleIntegerValues(int areaId)1262 private List<Integer> getPossibleIntegerValues(int areaId) { 1263 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig(); 1264 List<Integer> possibleValues = new ArrayList<>(); 1265 if (mPropertyId == VehiclePropertyIds.HVAC_FAN_DIRECTION) { 1266 int[] availableHvacFanDirections = mCarPropertyManager.getIntArrayProperty( 1267 VehiclePropertyIds.HVAC_FAN_DIRECTION_AVAILABLE, areaId); 1268 for (int i = 0; i < availableHvacFanDirections.length; i++) { 1269 if (availableHvacFanDirections[i] != CarHvacFanDirection.UNKNOWN) { 1270 possibleValues.add(availableHvacFanDirections[i]); 1271 } 1272 } 1273 return possibleValues; 1274 } 1275 if (mVerifySetterWithConfigArrayValues) { 1276 for (Integer value : carPropertyConfig.getConfigArray()) { 1277 possibleValues.add(value); 1278 } 1279 return possibleValues; 1280 } 1281 1282 if (!mAllPossibleEnumValues.isEmpty() && isAtLeastU()) { 1283 AreaIdConfig areaIdConfig = carPropertyConfig.getAreaIdConfig(areaId); 1284 for (Integer value : (List<Integer>) areaIdConfig.getSupportedEnumValues()) { 1285 if ((mAllPossibleUnwritableValues.isEmpty() 1286 || !mAllPossibleUnwritableValues.contains(value)) 1287 && (mAllPossibleUnavailableValues.isEmpty() 1288 || !mAllPossibleUnavailableValues.contains(value))) { 1289 possibleValues.add(value); 1290 } 1291 } 1292 } else { 1293 Integer minValue = (Integer) carPropertyConfig.getMinValue(areaId); 1294 Integer maxValue = (Integer) carPropertyConfig.getMaxValue(areaId); 1295 assertWithMessage("Read-write/Write integer properties should either have a config " 1296 + "array with valid set values, a set of supported enums, or valid min and max " 1297 + "values set in the CarPropertyConfig. However, the following property has " 1298 + "none of these: " + VehiclePropertyIds.toString(mPropertyId)) 1299 .that(minValue != null && maxValue != null).isTrue(); 1300 List<Integer> valuesToSet = IntStream.rangeClosed( 1301 minValue.intValue(), maxValue.intValue()).boxed().collect(Collectors.toList()); 1302 for (int i = 0; i < valuesToSet.size(); i++) { 1303 possibleValues.add(valuesToSet.get(i)); 1304 } 1305 } 1306 return possibleValues; 1307 } 1308 1309 /** 1310 * Gets the possible values for a float property. 1311 */ getPossibleFloatValues(int areaId)1312 private Collection<T> getPossibleFloatValues(int areaId) { 1313 ImmutableSet.Builder<Float> possibleValuesBuilder = ImmutableSet.builder(); 1314 if (mPropertyId == VehiclePropertyIds.HVAC_TEMPERATURE_SET) { 1315 List<Integer> hvacTempSetConfigArray = getCarPropertyConfig().getConfigArray(); 1316 if (!hvacTempSetConfigArray.isEmpty()) { 1317 // For HVAC_TEMPERATURE_SET, the configArray specifies the supported temperature 1318 // values for the property. configArray[0] is the lower bound of the supported 1319 // temperatures in Celsius. configArray[1] is the upper bound of the supported 1320 // temperatures in Celsius. configArray[2] is the supported temperature increment 1321 // between the two bounds. All configArray values are Celsius*10 since the 1322 // configArray is List<Integer> but HVAC_TEMPERATURE_SET is a Float type property. 1323 for (int possibleHvacTempSetValue = hvacTempSetConfigArray.get(0); 1324 possibleHvacTempSetValue <= hvacTempSetConfigArray.get(1); 1325 possibleHvacTempSetValue += hvacTempSetConfigArray.get(2)) { 1326 possibleValuesBuilder.add((float) possibleHvacTempSetValue / 10.0f); 1327 } 1328 } else { 1329 // If the configArray is not specified, then use min/max values. 1330 Float minValueFloat = (Float) getCarPropertyConfig().getMinValue(areaId); 1331 Float maxValueFloat = (Float) getCarPropertyConfig().getMaxValue(areaId); 1332 possibleValuesBuilder.add(minValueFloat); 1333 possibleValuesBuilder.add(maxValueFloat); 1334 } 1335 } else if (mPropertyId == VehiclePropertyIds.EV_CHARGE_PERCENT_LIMIT) { 1336 List<Integer> evChargePercentLimitConfigArray = getCarPropertyConfig().getConfigArray(); 1337 if (!evChargePercentLimitConfigArray.isEmpty()) { 1338 for (Integer possibleEvChargePercentLimit : evChargePercentLimitConfigArray) { 1339 possibleValuesBuilder.add(possibleEvChargePercentLimit.floatValue()); 1340 } 1341 } else { 1342 // If the configArray is not specified, then values between 0 and 100 percent must 1343 // be supported. 1344 possibleValuesBuilder.add(0f); 1345 possibleValuesBuilder.add(100f); 1346 } 1347 } else if (mPropertyId == VehiclePropertyIds.EV_CHARGE_CURRENT_DRAW_LIMIT) { 1348 // First value in the configArray specifies the max current draw allowed by the vehicle. 1349 Integer vehicleMaxCurrentDrawLimit = getCarPropertyConfig().getConfigArray().get(0); 1350 possibleValuesBuilder.add(vehicleMaxCurrentDrawLimit.floatValue()); 1351 } else if (mPropertyId == VehiclePropertyIds.RANGE_REMAINING) { 1352 // Test when no range is remaining 1353 possibleValuesBuilder.add(0f); 1354 } 1355 return (Collection<T>) possibleValuesBuilder.build(); 1356 } 1357 verifyCarPropertyValueSetter()1358 private void verifyCarPropertyValueSetter() { 1359 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig(); 1360 if (!AREA_ID_CONFIG_ACCESS_FLAG && carPropertyConfig.getAccess() 1361 == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ) { 1362 verifySetPropertyFails(carPropertyConfig.getAreaIds()[0]); 1363 return; 1364 } 1365 if (Boolean.class.equals(carPropertyConfig.getPropertyType())) { 1366 verifyBooleanPropertySetter(); 1367 } else if (Integer.class.equals(carPropertyConfig.getPropertyType())) { 1368 verifyIntegerPropertySetter(); 1369 } else if (Float.class.equals(carPropertyConfig.getPropertyType())) { 1370 verifyFloatPropertySetter(); 1371 } else if (mPropertyId == VehiclePropertyIds.HVAC_TEMPERATURE_VALUE_SUGGESTION) { 1372 verifyHvacTemperatureValueSuggestionSetter(); 1373 } 1374 } 1375 verifySetPropertyFails(int areaId)1376 private void verifySetPropertyFails(int areaId) { 1377 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig(); 1378 assertThrows( 1379 mPropertyName 1380 + " is a read_only property so setProperty should throw an" 1381 + " IllegalArgumentException.", 1382 IllegalArgumentException.class, 1383 () -> mCarPropertyManager.setProperty(mPropertyType, mPropertyId, areaId, 1384 getDefaultValue(mPropertyType))); 1385 } 1386 verifyBooleanPropertySetter()1387 private void verifyBooleanPropertySetter() { 1388 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig(); 1389 for (int areaId : carPropertyConfig.getAreaIds()) { 1390 if (doesAreaIdAccessMatch(carPropertyConfig, areaId, 1391 CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ)) { 1392 verifySetPropertyFails(areaId); 1393 continue; 1394 } 1395 for (Boolean valueToSet: List.of(Boolean.TRUE, Boolean.FALSE)) { 1396 verifySetProperty(areaId, (T) valueToSet); 1397 } 1398 } 1399 } 1400 1401 verifyIntegerPropertySetter()1402 private void verifyIntegerPropertySetter() { 1403 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig(); 1404 for (int areaId : carPropertyConfig.getAreaIds()) { 1405 if (doesAreaIdAccessMatch(carPropertyConfig, areaId, 1406 CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ)) { 1407 verifySetPropertyFails(areaId); 1408 continue; 1409 } 1410 for (Integer valueToSet : getPossibleIntegerValues(areaId)) { 1411 verifySetProperty(areaId, (T) valueToSet); 1412 } 1413 } 1414 if (!mAllPossibleEnumValues.isEmpty() && isAtLeastU()) { 1415 for (AreaIdConfig<?> areaIdConfig : carPropertyConfig.getAreaIdConfigs()) { 1416 if (doesAreaIdAccessMatch(carPropertyConfig, areaIdConfig.getAreaId(), 1417 CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ)) { 1418 continue; 1419 } 1420 for (T valueToSet : (List<T>) areaIdConfig.getSupportedEnumValues()) { 1421 if (!mAllPossibleUnwritableValues.isEmpty() 1422 && mAllPossibleUnwritableValues.contains(valueToSet)) { 1423 assertThrows("Trying to set an unwritable value: " + valueToSet 1424 + " to property: " + mPropertyId + " should throw an " 1425 + "IllegalArgumentException", 1426 IllegalArgumentException.class, 1427 () -> setPropertyAndWaitForChange( 1428 mCarPropertyManager, mPropertyId, 1429 carPropertyConfig.getPropertyType(), 1430 areaIdConfig.getAreaId(), valueToSet)); 1431 } 1432 if (!mAllPossibleUnavailableValues.isEmpty() 1433 && mAllPossibleUnavailableValues.contains(valueToSet)) { 1434 assertThrows("Trying to set an unavailable value: " + valueToSet 1435 + " to property: " + mPropertyId + " should throw an " 1436 + "PropertyNotAvailableException", 1437 PropertyNotAvailableException.class, 1438 () -> mCarPropertyManager.setProperty( 1439 carPropertyConfig.getPropertyType(), mPropertyId, 1440 areaIdConfig.getAreaId(), valueToSet)); 1441 } 1442 } 1443 } 1444 } 1445 } 1446 verifyFloatPropertySetter()1447 private void verifyFloatPropertySetter() { 1448 for (int areaId : getCarPropertyConfig().getAreaIds()) { 1449 for (T valueToSet : getPossibleFloatValues(areaId)) { 1450 verifySetProperty(areaId, valueToSet); 1451 } 1452 } 1453 } 1454 verifySetProperty(int areaId, T valueToSet)1455 private void verifySetProperty(int areaId, T valueToSet) { 1456 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig(); 1457 if (doesAreaIdAccessMatch(carPropertyConfig, areaId, 1458 CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ)) { 1459 return; 1460 } 1461 1462 verifySetPropertyWithNullValueThrowsException(areaId); 1463 1464 if (getAreaIdAccessOrElseGlobalAccess(carPropertyConfig, areaId) 1465 == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE) { 1466 Log.w(TAG, "Property: " + mPropertyName + " will be altered during the test and it is" 1467 + " not possible to restore."); 1468 verifySetPropertyOkayOrThrowExpectedExceptions(areaId, valueToSet); 1469 return; 1470 } 1471 try { 1472 CarPropertyValue<T> currentCarPropertyValue = 1473 mCarPropertyManager.getProperty(mPropertyId, areaId); 1474 verifyCarPropertyValue(currentCarPropertyValue, areaId, 1475 CAR_PROPERTY_VALUE_SOURCE_GETTER); 1476 if (valueEquals(valueToSet, currentCarPropertyValue.getValue())) { 1477 return; 1478 } 1479 } catch (PropertyNotAvailableException e) { 1480 verifyPropertyNotAvailableException(e); 1481 } catch (CarInternalErrorException e) { 1482 verifyInternalErrorException(e); 1483 } 1484 CarPropertyValue<T> updatedCarPropertyValue = setPropertyAndWaitForChange( 1485 mCarPropertyManager, mPropertyId, carPropertyConfig.getPropertyType(), areaId, 1486 valueToSet); 1487 if (sExceptionClassOnSet == null) { 1488 verifyCarPropertyValue(updatedCarPropertyValue, areaId, 1489 CAR_PROPERTY_VALUE_SOURCE_CALLBACK); 1490 } 1491 } 1492 verifySetPropertyWithNullValueThrowsException(int areaId)1493 private void verifySetPropertyWithNullValueThrowsException(int areaId) { 1494 assertThrows(NullPointerException.class, () -> 1495 mCarPropertyManager.setProperty(mPropertyType, mPropertyId, areaId, 1496 /* val= */ null)); 1497 } 1498 verifyHvacTemperatureValueSuggestionSetter()1499 private void verifyHvacTemperatureValueSuggestionSetter() { 1500 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig(); 1501 CarPropertyConfig<?> hvacTemperatureSetCarPropertyConfig = 1502 getCarPropertyConfig(VehiclePropertyIds.HVAC_TEMPERATURE_SET); 1503 if (hvacTemperatureSetCarPropertyConfig == null) { 1504 return; 1505 } 1506 List<Integer> hvacTemperatureSetConfigArray = 1507 hvacTemperatureSetCarPropertyConfig.getConfigArray(); 1508 if (hvacTemperatureSetConfigArray.isEmpty()) { 1509 return; 1510 } 1511 float minTempInCelsius = hvacTemperatureSetConfigArray.get(0).floatValue() / 10f; 1512 float minTempInFahrenheit = hvacTemperatureSetConfigArray.get(3).floatValue() / 10f; 1513 1514 Float[] temperatureRequest = new Float[] { 1515 /* requestedValue = */ minTempInCelsius, 1516 /* units = */ (float) 0x30, // VehicleUnit#CELSIUS 1517 /* suggestedValueInCelsius = */ 0f, 1518 /* suggestedValueInFahrenheit = */ 0f 1519 }; 1520 Float[] expectedTemperatureResponse = new Float[] { 1521 /* requestedValue = */ minTempInCelsius, 1522 /* units = */ (float) 0x30, // VehicleUnit#CELSIUS 1523 /* suggestedValueInCelsius = */ minTempInCelsius, 1524 /* suggestedValueInFahrenheit = */ minTempInFahrenheit 1525 }; 1526 for (int areaId: carPropertyConfig.getAreaIds()) { 1527 CarPropertyValue<Float[]> updatedCarPropertyValue = setPropertyAndWaitForChange( 1528 mCarPropertyManager, mPropertyId, Float[].class, areaId, 1529 temperatureRequest, expectedTemperatureResponse); 1530 verifyCarPropertyValue(updatedCarPropertyValue, areaId, 1531 CAR_PROPERTY_VALUE_SOURCE_CALLBACK); 1532 verifyHvacTemperatureValueSuggestionResponse(updatedCarPropertyValue.getValue()); 1533 } 1534 } 1535 verifySetPropertyOkayOrThrowExpectedExceptions(int areaId, T valueToSet)1536 private void verifySetPropertyOkayOrThrowExpectedExceptions(int areaId, T valueToSet) { 1537 try { 1538 mCarPropertyManager.setProperty(mPropertyType, mPropertyId, areaId, valueToSet); 1539 } catch (PropertyNotAvailableAndRetryException e) { 1540 } catch (PropertyNotAvailableException e) { 1541 verifyPropertyNotAvailableException(e); 1542 } catch (CarInternalErrorException e) { 1543 verifyInternalErrorException(e); 1544 } catch (Exception e) { 1545 assertWithMessage("Unexpected exception thrown when trying to setProperty on " 1546 + mPropertyName + ": " + e).fail(); 1547 } 1548 } 1549 verifyGetNotAvailable()1550 private void verifyGetNotAvailable() { 1551 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig(); 1552 for (int areaId : carPropertyConfig.getAreaIds()) { 1553 try { 1554 // getProperty may/may not throw exception when the property is not available. 1555 CarPropertyValue<T> currentValue = 1556 mCarPropertyManager.getProperty(mPropertyId, areaId); 1557 assertWithMessage("When the power is turned off getProperty should throw" 1558 + " PropertyNotAvailableException when trying to get a" 1559 + " property with StatusCode.NOT_AVAILABLE or return a" 1560 + " CarPropertyValue with status UNAVAILABLE." 1561 + " Returned CarPropertyValue: " + currentValue.toString()) 1562 .that(currentValue.getStatus()) 1563 .isEqualTo(CarPropertyValue.STATUS_UNAVAILABLE); 1564 } catch (Exception e) { 1565 // If the property is read or read-write, then this should throw 1566 // PropertyNotAvailableException. If the property is write-only, then it will throw 1567 // IllegalArgumentException. 1568 assertWithMessage( 1569 "Getting property " + mPropertyName + " when it's not available" 1570 + " should throw either PropertyNotAvailableException or" 1571 + " IllegalArgumentException.") 1572 .that(e.getClass()) 1573 .isAnyOf(PropertyNotAvailableException.class, 1574 IllegalArgumentException.class); 1575 } 1576 } 1577 } 1578 verifySetNotAvailable()1579 private void verifySetNotAvailable() { 1580 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig(); 1581 if (!AREA_ID_CONFIG_ACCESS_FLAG && carPropertyConfig.getAccess() 1582 != CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE) { 1583 return; 1584 } 1585 1586 T valueToSet = getDefaultValue(mPropertyType); 1587 if (valueToSet == null) { 1588 assertWithMessage("Testing mixed type property is not supported").fail(); 1589 } 1590 for (int areaId : carPropertyConfig.getAreaIds()) { 1591 if (doesAreaIdAccessNotMatch(carPropertyConfig, areaId, 1592 CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE)) { 1593 continue; 1594 } 1595 1596 SetterCallback setterCallback = new SetterCallback(mPropertyId, areaId, valueToSet); 1597 assertWithMessage("Failed to register no change setter callback for " 1598 + VehiclePropertyIds.toString(mPropertyId)) 1599 .that(subscribePropertyEvents(mCarPropertyManager, setterCallback, mPropertyId, 1600 CarPropertyManager.SENSOR_RATE_FASTEST)).isTrue(); 1601 1602 try { 1603 mCarPropertyManager.setProperty(mPropertyType, mPropertyId, areaId, valueToSet); 1604 CarPropertyValue<T> updatedValue = 1605 setterCallback.waitForPropertyEvent(SET_PROPERTY_CALLBACK_TIMEOUT_SEC); 1606 if (updatedValue != null 1607 && updatedValue.getStatus() == CarPropertyValue.STATUS_AVAILABLE) { 1608 // If the callback receives a new event with the value set before the timeout, 1609 // then this check will fail. 1610 assertWithMessage( 1611 "Received onChangeEvent(s) for " + mPropertyName 1612 + " with updated value: " + valueToSet + " before 5s timeout." 1613 + " When the power is turned off, this property must not be" 1614 + " available to set.") 1615 .that(updatedValue.getValue()).isNotEqualTo(valueToSet); 1616 } 1617 } catch (Exception e) { 1618 // In normal cases, this should throw PropertyNotAvailableException. 1619 // In rare cases, the value we are setting is the same as the current value, 1620 // which makes the set operation a no-op. So it is possible that no exception 1621 // is thrown here. 1622 // It is also possible that this may throw IllegalArgumentException if the value to 1623 // set is not valid. 1624 assertWithMessage( 1625 "Setting property " + mPropertyName + " when it's not available" 1626 + " should throw either PropertyNotAvailableException or" 1627 + " IllegalArgumentException.") 1628 .that(e.getClass()) 1629 .isAnyOf(PropertyNotAvailableException.class, 1630 IllegalArgumentException.class); 1631 } finally { 1632 unsubscribePropertyEvents(mCarPropertyManager, setterCallback, mPropertyId); 1633 } 1634 } 1635 } 1636 verifyAdasPropertyDisabled(boolean verifySet)1637 private void verifyAdasPropertyDisabled(boolean verifySet) { 1638 if (!mVerifyErrorStates) { 1639 verifyGetNotAvailable(); 1640 if (verifySet) { 1641 verifySetNotAvailable(); 1642 } 1643 return; 1644 } 1645 1646 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig(); 1647 if (!AREA_ID_CONFIG_ACCESS_FLAG && carPropertyConfig.getAccess() 1648 == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE) { 1649 return; 1650 } 1651 1652 for (int areaId : carPropertyConfig.getAreaIds()) { 1653 if (doesAreaIdAccessMatch(carPropertyConfig, areaId, 1654 CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE)) { 1655 continue; 1656 } 1657 Integer adasState = mCarPropertyManager.getIntProperty(mPropertyId, areaId); 1658 assertWithMessage( 1659 "When ADAS feature is disabled, " 1660 + VehiclePropertyIds.toString(mPropertyId) 1661 + " must be set to " + ErrorState.NOT_AVAILABLE_DISABLED 1662 + " (ErrorState.NOT_AVAILABLE_DISABLED).") 1663 .that(adasState) 1664 .isEqualTo(ErrorState.NOT_AVAILABLE_DISABLED); 1665 } 1666 } 1667 getUpdatesPerAreaId(int changeMode)1668 private static int getUpdatesPerAreaId(int changeMode) { 1669 return changeMode != CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS 1670 ? 1 : 2; 1671 } 1672 getRegisterCallbackTimeoutMillis(int changeMode, float minSampleRate)1673 private static long getRegisterCallbackTimeoutMillis(int changeMode, float minSampleRate) { 1674 long timeoutMillis = 1500; 1675 if (changeMode == CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS) { 1676 float secondsToMillis = 1_000; 1677 long bufferMillis = 1_000; // 1 second 1678 timeoutMillis = ((long) ((1.0f / minSampleRate) * secondsToMillis 1679 * getUpdatesPerAreaId(changeMode))) + bufferMillis; 1680 } 1681 return timeoutMillis; 1682 } 1683 subscribePropertyEvents(CarPropertyManager carPropertyManager, CarPropertyManager.CarPropertyEventCallback callback, int propertyId, float updateRateHz)1684 private static boolean subscribePropertyEvents(CarPropertyManager carPropertyManager, 1685 CarPropertyManager.CarPropertyEventCallback callback, int propertyId, 1686 float updateRateHz) { 1687 if (isAtLeastV() && Flags.variableUpdateRate()) { 1688 // Use new API if at least V. 1689 return carPropertyManager.subscribePropertyEvents(List.of( 1690 new Subscription.Builder(propertyId).setUpdateRateHz(updateRateHz) 1691 .setVariableUpdateRateEnabled(false).build()), 1692 /* callbackExecutor= */ null, callback); 1693 } else { 1694 return carPropertyManager.registerCallback(callback, propertyId, updateRateHz); 1695 } 1696 } 1697 subscribePropertyEvents(CarPropertyManager.CarPropertyEventCallback callback, int propertyId, float updateRateHz)1698 private boolean subscribePropertyEvents(CarPropertyManager.CarPropertyEventCallback callback, 1699 int propertyId, float updateRateHz) { 1700 return subscribePropertyEvents(mCarPropertyManager, callback, propertyId, updateRateHz); 1701 } 1702 unsubscribePropertyEvents(CarPropertyManager carPropertyManager, CarPropertyManager.CarPropertyEventCallback callback, int propertyId)1703 private static void unsubscribePropertyEvents(CarPropertyManager carPropertyManager, 1704 CarPropertyManager.CarPropertyEventCallback callback, int propertyId) { 1705 if (isAtLeastV() && Flags.variableUpdateRate()) { 1706 // Use new API if at least V. 1707 carPropertyManager.unsubscribePropertyEvents(propertyId, callback); 1708 } else { 1709 carPropertyManager.unregisterCallback(callback, propertyId); 1710 } 1711 } 1712 unsubscribePropertyEvents(CarPropertyManager.CarPropertyEventCallback callback, int propertyId)1713 private void unsubscribePropertyEvents(CarPropertyManager.CarPropertyEventCallback callback, 1714 int propertyId) { 1715 unsubscribePropertyEvents(mCarPropertyManager, callback, propertyId); 1716 } 1717 verifyCarPropertyValueCallback()1718 private void verifyCarPropertyValueCallback() { 1719 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig(); 1720 if ((AREA_ID_CONFIG_ACCESS_FLAG ? carPropertyConfig.getAreaIdConfigs().get(0).getAccess() 1721 : carPropertyConfig.getAccess()) 1722 == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE) { 1723 // This means we specify read permission for one property, but the OEM specify it as 1724 // write-only. This will only happen for properties that we allow READ_WRITE or WRITE. 1725 // We currently do not have such system property. 1726 return; 1727 } 1728 int updatesPerAreaId = getUpdatesPerAreaId(mChangeMode); 1729 long timeoutMillis = getRegisterCallbackTimeoutMillis(mChangeMode, 1730 carPropertyConfig.getMinSampleRate()); 1731 1732 CarPropertyValueCallback carPropertyValueCallback = new CarPropertyValueCallback( 1733 mPropertyName, carPropertyConfig.getAreaIds(), updatesPerAreaId, timeoutMillis); 1734 assertWithMessage("Failed to register callback for " + mPropertyName) 1735 .that( 1736 subscribePropertyEvents(carPropertyValueCallback, mPropertyId, 1737 carPropertyConfig.getMaxSampleRate())) 1738 .isTrue(); 1739 SparseArray<List<CarPropertyValue<?>>> areaIdToCarPropertyValues = 1740 carPropertyValueCallback.getAreaIdToCarPropertyValues(); 1741 unsubscribePropertyEvents(carPropertyValueCallback, mPropertyId); 1742 1743 for (int areaId : carPropertyConfig.getAreaIds()) { 1744 List<CarPropertyValue<?>> carPropertyValues = areaIdToCarPropertyValues.get(areaId); 1745 assertWithMessage( 1746 mPropertyName + " callback value list is null for area ID: " + areaId).that( 1747 carPropertyValues).isNotNull(); 1748 assertWithMessage(mPropertyName + " callback values did not receive " + updatesPerAreaId 1749 + " updates for area ID: " + areaId).that(carPropertyValues.size()).isAtLeast( 1750 updatesPerAreaId); 1751 for (CarPropertyValue<?> carPropertyValue : carPropertyValues) { 1752 verifyCarPropertyValue(carPropertyValue, carPropertyValue.getAreaId(), 1753 CAR_PROPERTY_VALUE_SOURCE_CALLBACK); 1754 if (mPropertyId == VehiclePropertyIds.HVAC_TEMPERATURE_VALUE_SUGGESTION) { 1755 verifyHvacTemperatureValueSuggestionResponse( 1756 (Float[]) carPropertyValue.getValue()); 1757 } 1758 } 1759 } 1760 } 1761 verifyAccess_isSubsetOfOtherAccess(int subAccess, int superAccess)1762 private void verifyAccess_isSubsetOfOtherAccess(int subAccess, int superAccess) { 1763 if (superAccess == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE) { 1764 assertWithMessage( 1765 mPropertyName 1766 + " must be " 1767 + accessToString(CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ) 1768 + " or " 1769 + accessToString( 1770 CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE)) 1771 .that(subAccess) 1772 .isIn( 1773 ImmutableSet.of( 1774 CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ, 1775 CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE)); 1776 } else { 1777 assertWithMessage(mPropertyName + " must be " + accessToString(superAccess)) 1778 .that(subAccess) 1779 .isEqualTo(superAccess); 1780 } 1781 } 1782 verifyCarPropertyConfig()1783 private void verifyCarPropertyConfig() { 1784 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig(); 1785 assertWithMessage(mPropertyName + " CarPropertyConfig must have correct property ID") 1786 .that(carPropertyConfig.getPropertyId()) 1787 .isEqualTo(mPropertyId); 1788 int carPropConfigAccess = carPropertyConfig.getAccess(); 1789 verifyAccess_isSubsetOfOtherAccess(carPropConfigAccess, mAccess); 1790 if (AREA_ID_CONFIG_ACCESS_FLAG) { 1791 for (AreaIdConfig<?> areaIdConfig : carPropertyConfig.getAreaIdConfigs()) { 1792 int areaAccess = areaIdConfig.getAccess(); 1793 verifyAccess_isSubsetOfOtherAccess(areaAccess, mAccess); 1794 verifyAccess_isSubsetOfOtherAccess(carPropConfigAccess, areaAccess); 1795 } 1796 } 1797 assertWithMessage(mPropertyName + " must be " + areaTypeToString(mAreaType)) 1798 .that(carPropertyConfig.getAreaType()) 1799 .isEqualTo(mAreaType); 1800 assertWithMessage(mPropertyName + " must be " + changeModeToString(mChangeMode)) 1801 .that(carPropertyConfig.getChangeMode()) 1802 .isEqualTo(mChangeMode); 1803 assertWithMessage(mPropertyName + " must be " + mPropertyType + " type property") 1804 .that(carPropertyConfig.getPropertyType()) 1805 .isEqualTo(mPropertyType); 1806 1807 int[] areaIds = carPropertyConfig.getAreaIds(); 1808 assertWithMessage(mPropertyName + "'s must have at least 1 area ID defined") 1809 .that(areaIds.length).isAtLeast(1); 1810 assertWithMessage(mPropertyName + "'s area IDs must all be unique: " + Arrays.toString( 1811 areaIds)).that(ImmutableSet.copyOf(Arrays.stream( 1812 areaIds).boxed().collect(Collectors.toList())).size() 1813 == areaIds.length).isTrue(); 1814 1815 if (mAreaType == VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL) { 1816 assertWithMessage( 1817 mPropertyName 1818 + "'s AreaIds must contain a single 0 since it is " 1819 + areaTypeToString(mAreaType)) 1820 .that(areaIds) 1821 .isEqualTo(new int[] {0}); 1822 } else if (mAreaType == VehicleAreaType.VEHICLE_AREA_TYPE_WHEEL) { 1823 verifyValidAreaIdsForAreaType(ALL_POSSIBLE_WHEEL_AREA_IDS); 1824 verifyNoAreaOverlapInAreaIds(WHEEL_AREAS); 1825 } else if (mAreaType == VehicleAreaType.VEHICLE_AREA_TYPE_WINDOW) { 1826 verifyValidAreaIdsForAreaType(ALL_POSSIBLE_WINDOW_AREA_IDS); 1827 verifyNoAreaOverlapInAreaIds(WINDOW_AREAS); 1828 } else if (mAreaType == VehicleAreaType.VEHICLE_AREA_TYPE_MIRROR) { 1829 verifyValidAreaIdsForAreaType(ALL_POSSIBLE_MIRROR_AREA_IDS); 1830 verifyNoAreaOverlapInAreaIds(MIRROR_AREAS); 1831 } else if (mAreaType == VehicleAreaType.VEHICLE_AREA_TYPE_SEAT 1832 && mPropertyId != VehiclePropertyIds.INFO_DRIVER_SEAT) { 1833 verifyValidAreaIdsForAreaType(ALL_POSSIBLE_SEAT_AREA_IDS); 1834 verifyNoAreaOverlapInAreaIds(SEAT_AREAS); 1835 } else if (mAreaType == VehicleAreaType.VEHICLE_AREA_TYPE_DOOR) { 1836 verifyValidAreaIdsForAreaType(ALL_POSSIBLE_DOOR_AREA_IDS); 1837 verifyNoAreaOverlapInAreaIds(DOOR_AREAS); 1838 } else if (mAreaType == VehicleAreaType.VEHICLE_AREA_TYPE_VENDOR) { 1839 assertWithMessage(mPropertyName 1840 + " has an unsupported area type " 1841 + areaTypeToString(mAreaType) 1842 + " since associated feature flag is false") 1843 .that(Flags.androidVicVehicleProperties()) 1844 .isTrue(); 1845 1846 ImmutableSet<Integer> setOfAreaIds = 1847 ImmutableSet.copyOf(Arrays.stream(areaIds).boxed().collect(Collectors.toSet())); 1848 verifyNoAreaOverlapInAreaIds(setOfAreaIds); 1849 } 1850 1851 if (mAreaIdsVerifier.isPresent()) { 1852 mAreaIdsVerifier.get().verify(mVerifierContext, areaIds); 1853 } 1854 1855 if (mChangeMode == CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS) { 1856 verifyContinuousCarPropertyConfig(); 1857 } else { 1858 verifyNonContinuousCarPropertyConfig(); 1859 } 1860 1861 mCarPropertyConfigVerifier.ifPresent( 1862 carPropertyConfigVerifier -> carPropertyConfigVerifier.verify(mVerifierContext, 1863 carPropertyConfig)); 1864 1865 if (!mPossibleConfigArrayValues.isEmpty()) { 1866 assertWithMessage(mPropertyName + " configArray must specify supported values") 1867 .that(carPropertyConfig.getConfigArray().size()) 1868 .isGreaterThan(0); 1869 for (Integer supportedValue : carPropertyConfig.getConfigArray()) { 1870 assertWithMessage( 1871 mPropertyName 1872 + " configArray value must be a defined " 1873 + "value: " 1874 + supportedValue) 1875 .that(supportedValue) 1876 .isIn(mPossibleConfigArrayValues); 1877 } 1878 } 1879 1880 mConfigArrayVerifier.ifPresent(configArrayVerifier -> configArrayVerifier.verify( 1881 mVerifierContext, carPropertyConfig.getConfigArray())); 1882 1883 if (mPossibleConfigArrayValues.isEmpty() && !mConfigArrayVerifier.isPresent() 1884 && !mCarPropertyConfigVerifier.isPresent()) { 1885 assertWithMessage(mPropertyName + " configArray is undefined, so it must be empty") 1886 .that(carPropertyConfig.getConfigArray().size()) 1887 .isEqualTo(0); 1888 } 1889 1890 for (int areaId : areaIds) { 1891 T areaIdMinValue = (T) carPropertyConfig.getMinValue(areaId); 1892 T areaIdMaxValue = (T) carPropertyConfig.getMaxValue(areaId); 1893 if (mRequireMinMaxValues) { 1894 assertWithMessage(mPropertyName + " - area ID: " + areaId 1895 + " must have min value defined").that(areaIdMinValue).isNotNull(); 1896 assertWithMessage(mPropertyName + " - area ID: " + areaId 1897 + " must have max value defined").that(areaIdMaxValue).isNotNull(); 1898 } 1899 if (mRequireMinValuesToBeZero) { 1900 assertWithMessage( 1901 mPropertyName + " - area ID: " + areaId + " min value must be zero").that( 1902 areaIdMinValue).isEqualTo(0); 1903 } 1904 if (mRequireZeroToBeContainedInMinMaxRanges) { 1905 assertWithMessage(mPropertyName + " - areaId: " + areaId 1906 + "'s max and min range must contain zero").that( 1907 verifyMaxAndMinRangeContainsZero(areaIdMinValue, areaIdMaxValue)).isTrue(); 1908 1909 } 1910 if (areaIdMinValue != null || areaIdMaxValue != null) { 1911 assertWithMessage( 1912 mPropertyName 1913 + " - areaId: " 1914 + areaId 1915 + "'s max value must be >= min value") 1916 .that(verifyMaxAndMin(areaIdMinValue, areaIdMaxValue)) 1917 .isTrue(); 1918 } 1919 1920 if (mRequirePropertyValueToBeInConfigArray && isAtLeastU()) { 1921 List<?> supportedEnumValues = carPropertyConfig.getAreaIdConfig( 1922 areaId).getSupportedEnumValues(); 1923 assertWithMessage(mPropertyName + " - areaId: " + areaId 1924 + "'s supported enum values must match the values in the config array.") 1925 .that(carPropertyConfig.getConfigArray()) 1926 .containsExactlyElementsIn(supportedEnumValues); 1927 } 1928 1929 if (mChangeMode == CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE 1930 && !mAllPossibleEnumValues.isEmpty() && isAtLeastU()) { 1931 List<?> supportedEnumValues = carPropertyConfig.getAreaIdConfig( 1932 areaId).getSupportedEnumValues(); 1933 assertWithMessage(mPropertyName + " - areaId: " + areaId 1934 + "'s supported enum values must be defined").that( 1935 supportedEnumValues).isNotEmpty(); 1936 assertWithMessage(mPropertyName + " - areaId: " + areaId 1937 + "'s supported enum values must not contain any duplicates").that( 1938 supportedEnumValues).containsNoDuplicates(); 1939 assertWithMessage( 1940 mPropertyName + " - areaId: " + areaId + "'s supported enum values " 1941 + supportedEnumValues + " must all exist in all possible enum set " 1942 + mAllPossibleEnumValues).that( 1943 mAllPossibleEnumValues.containsAll(supportedEnumValues)).isTrue(); 1944 } else if (isAtLeastU()) { 1945 assertWithMessage(mPropertyName + " - areaId: " + areaId 1946 + "'s supported enum values must be empty since property does not support" 1947 + " an enum").that( 1948 carPropertyConfig.getAreaIdConfig( 1949 areaId).getSupportedEnumValues()).isEmpty(); 1950 } 1951 } 1952 } 1953 verifyMaxAndMinRangeContainsZero(T min, T max)1954 private boolean verifyMaxAndMinRangeContainsZero(T min, T max) { 1955 int propertyType = mPropertyId & VehiclePropertyType.MASK; 1956 switch (propertyType) { 1957 case VehiclePropertyType.INT32: 1958 return (Integer) max >= 0 && (Integer) min <= 0; 1959 case VehiclePropertyType.INT64: 1960 return (Long) max >= 0 && (Long) min <= 0; 1961 case VehiclePropertyType.FLOAT: 1962 return (Float) max >= 0 && (Float) min <= 0; 1963 default: 1964 return false; 1965 } 1966 } 1967 verifyMaxAndMin(T min, T max)1968 private boolean verifyMaxAndMin(T min, T max) { 1969 int propertyType = mPropertyId & VehiclePropertyType.MASK; 1970 switch (propertyType) { 1971 case VehiclePropertyType.INT32: 1972 return (Integer) max >= (Integer) min; 1973 case VehiclePropertyType.INT64: 1974 return (Long) max >= (Long) min; 1975 case VehiclePropertyType.FLOAT: 1976 return (Float) max >= (Float) min; 1977 default: 1978 return false; 1979 } 1980 } 1981 verifyContinuousCarPropertyConfig()1982 private void verifyContinuousCarPropertyConfig() { 1983 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig(); 1984 assertWithMessage( 1985 mPropertyName 1986 + " must define max sample rate since change mode is " 1987 + changeModeToString(mChangeMode)) 1988 .that(carPropertyConfig.getMaxSampleRate()) 1989 .isGreaterThan(0); 1990 assertWithMessage( 1991 mPropertyName 1992 + " must define min sample rate since change mode is " 1993 + changeModeToString(mChangeMode)) 1994 .that(carPropertyConfig.getMinSampleRate()) 1995 .isGreaterThan(0); 1996 assertWithMessage(mPropertyName + " max sample rate must be >= min sample rate") 1997 .that(carPropertyConfig.getMaxSampleRate() >= carPropertyConfig.getMinSampleRate()) 1998 .isTrue(); 1999 } 2000 verifyNonContinuousCarPropertyConfig()2001 private void verifyNonContinuousCarPropertyConfig() { 2002 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig(); 2003 assertWithMessage( 2004 mPropertyName 2005 + " must define max sample rate as 0 since change mode is " 2006 + changeModeToString(mChangeMode)) 2007 .that(carPropertyConfig.getMaxSampleRate()) 2008 .isEqualTo(0); 2009 assertWithMessage( 2010 mPropertyName 2011 + " must define min sample rate as 0 since change mode is " 2012 + changeModeToString(mChangeMode)) 2013 .that(carPropertyConfig.getMinSampleRate()) 2014 .isEqualTo(0); 2015 } 2016 handleGetPropertyExceptions(Exception e)2017 private void handleGetPropertyExceptions(Exception e) { 2018 if (e instanceof PropertyNotAvailableException) { 2019 verifyPropertyNotAvailableException((PropertyNotAvailableException) e); 2020 } else if (e instanceof CarInternalErrorException) { 2021 verifyInternalErrorException((CarInternalErrorException) e); 2022 } 2023 sExceptionClassOnGet = e.getClass(); 2024 } 2025 handleClassSpecificGetPropertyExceptions(Exception e, Class<?> expectedClass, int areaId)2026 private void handleClassSpecificGetPropertyExceptions(Exception e, Class<?> expectedClass, 2027 int areaId) { 2028 if (e instanceof IllegalArgumentException) { 2029 if (mPropertyType.equals(expectedClass)) { 2030 assertWithMessage("getProperty for " + expectedClass + " class should not throw" 2031 + " IllegalArgumentException for valid propertyId: " + mPropertyId 2032 + " areaId: " + areaId + " of type: " + mPropertyType + " if property is" 2033 + " readable").fail(); 2034 } 2035 } else { 2036 handleGetPropertyExceptions(e); 2037 } 2038 } 2039 verifyClassSpecificGetPropertyResults(T value, Class<?> expectedClass, int areaId)2040 private void verifyClassSpecificGetPropertyResults(T value, Class<?> expectedClass, 2041 int areaId) { 2042 if (!mPropertyType.equals(expectedClass)) { 2043 assertWithMessage("getProperty for " + expectedClass + " class should throw" 2044 + " IllegalArgumentException for valid propertyId: " + mPropertyId + " areaId: " 2045 + areaId + " of" + " type: " + mPropertyType + " if property is readable") 2046 .fail(); 2047 } 2048 verifyCarPropertyValue(mPropertyId, areaId, CarPropertyValue.STATUS_AVAILABLE, 2049 SystemClock.elapsedRealtimeNanos(), value, areaId, 2050 CAR_PROPERTY_VALUE_SOURCE_GETTER); 2051 } 2052 verifyCarPropertyValueGetter()2053 private void verifyCarPropertyValueGetter() { 2054 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig(); 2055 if (!AREA_ID_CONFIG_ACCESS_FLAG && carPropertyConfig.getAccess() 2056 == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE) { 2057 verifyGetPropertyFails(carPropertyConfig.getAreaIds()[0]); 2058 return; 2059 } 2060 for (int areaId : carPropertyConfig.getAreaIds()) { 2061 if (doesAreaIdAccessMatch(carPropertyConfig, areaId, 2062 CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE)) { 2063 verifyGetPropertyFails(areaId); 2064 continue; 2065 } 2066 2067 CarPropertyValue<?> carPropertyValue = null; 2068 try { 2069 carPropertyValue = mCarPropertyManager.getProperty(mPropertyId, areaId); 2070 verifyCarPropertyValue(carPropertyValue, areaId, CAR_PROPERTY_VALUE_SOURCE_GETTER); 2071 if (mPropertyId == VehiclePropertyIds.HVAC_TEMPERATURE_VALUE_SUGGESTION) { 2072 verifyHvacTemperatureValueSuggestionResponse( 2073 (Float[]) carPropertyValue.getValue()); 2074 } 2075 } catch (PropertyNotAvailableException | CarInternalErrorException e) { 2076 handleGetPropertyExceptions(e); 2077 } 2078 2079 try { 2080 Boolean value = mCarPropertyManager.getBooleanProperty(mPropertyId, areaId); 2081 verifyClassSpecificGetPropertyResults((T) value, Boolean.class, areaId); 2082 } catch (IllegalArgumentException | PropertyNotAvailableException 2083 | CarInternalErrorException e) { 2084 handleClassSpecificGetPropertyExceptions(e, Boolean.class, areaId); 2085 } 2086 try { 2087 Integer value = mCarPropertyManager.getIntProperty(mPropertyId, areaId); 2088 verifyClassSpecificGetPropertyResults((T) value, Integer.class, areaId); 2089 } catch (IllegalArgumentException | PropertyNotAvailableException 2090 | CarInternalErrorException e) { 2091 handleClassSpecificGetPropertyExceptions(e, Integer.class, areaId); 2092 } 2093 try { 2094 Float value = mCarPropertyManager.getFloatProperty(mPropertyId, areaId); 2095 verifyClassSpecificGetPropertyResults((T) value, Float.class, areaId); 2096 } catch (IllegalArgumentException | PropertyNotAvailableException 2097 | CarInternalErrorException e) { 2098 handleClassSpecificGetPropertyExceptions(e, Float.class, areaId); 2099 } 2100 try { 2101 int[] primitiveArray = mCarPropertyManager.getIntArrayProperty(mPropertyId, areaId); 2102 Integer[] value = new Integer[primitiveArray.length]; 2103 for (int i = 0; i < primitiveArray.length; i++) { 2104 value[i] = (Integer) primitiveArray[i]; 2105 } 2106 verifyClassSpecificGetPropertyResults((T) value, Integer[].class, areaId); 2107 } catch (IllegalArgumentException | PropertyNotAvailableException 2108 | CarInternalErrorException e) { 2109 handleClassSpecificGetPropertyExceptions(e, Integer[].class, areaId); 2110 } 2111 } 2112 } 2113 verifyGetPropertyFails(int areaId)2114 private void verifyGetPropertyFails(int areaId) { 2115 assertGetPropertyThrowsException( 2116 mPropertyName 2117 + " is a write_only property so getProperty should throw an" 2118 + " IllegalArgumentException.", 2119 IllegalArgumentException.class, mPropertyId, areaId); 2120 } 2121 verifyPropertyNotAvailableException(PropertyNotAvailableException e)2122 private static void verifyPropertyNotAvailableException(PropertyNotAvailableException e) { 2123 if (!isAtLeastU()) { 2124 return; 2125 } 2126 assertThat(((PropertyNotAvailableException) e).getDetailedErrorCode()) 2127 .isIn(PROPERTY_NOT_AVAILABLE_ERROR_CODES); 2128 int vendorErrorCode = e.getVendorErrorCode(); 2129 assertThat(vendorErrorCode).isAtLeast(VENDOR_ERROR_CODE_MINIMUM_VALUE); 2130 assertThat(vendorErrorCode).isAtMost(VENDOR_ERROR_CODE_MAXIMUM_VALUE); 2131 } 2132 verifyInternalErrorException(CarInternalErrorException e)2133 private static void verifyInternalErrorException(CarInternalErrorException e) { 2134 if (!isAtLeastU()) { 2135 return; 2136 } 2137 int vendorErrorCode = e.getVendorErrorCode(); 2138 assertThat(vendorErrorCode).isAtLeast(VENDOR_ERROR_CODE_MINIMUM_VALUE); 2139 assertThat(vendorErrorCode).isAtMost(VENDOR_ERROR_CODE_MAXIMUM_VALUE); 2140 } 2141 verifyCarPropertyValue(CarPropertyValue<?> carPropertyValue, int expectedAreaId, String source)2142 private void verifyCarPropertyValue(CarPropertyValue<?> carPropertyValue, int expectedAreaId, 2143 String source) { 2144 verifyCarPropertyValue(carPropertyValue.getPropertyId(), 2145 carPropertyValue.getAreaId(), carPropertyValue.getStatus(), 2146 carPropertyValue.getTimestamp(), (T) carPropertyValue.getValue(), expectedAreaId, 2147 source); 2148 } 2149 verifyCarPropertyValue( int propertyId, int areaId, int status, long timestampNanos, T value, int expectedAreaId, String source)2150 private void verifyCarPropertyValue( 2151 int propertyId, int areaId, int status, long timestampNanos, T value, 2152 int expectedAreaId, String source) { 2153 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig(); 2154 mCarPropertyValueVerifier.ifPresent( 2155 propertyValueVerifier -> propertyValueVerifier.verify( 2156 mVerifierContext, carPropertyConfig, propertyId, 2157 areaId, timestampNanos, value)); 2158 assertWithMessage( 2159 mPropertyName 2160 + " - areaId: " 2161 + areaId 2162 + " - source: " 2163 + source 2164 + " value must have correct property ID") 2165 .that(propertyId) 2166 .isEqualTo(mPropertyId); 2167 assertWithMessage( 2168 mPropertyName 2169 + " - areaId: " 2170 + areaId 2171 + " - source: " 2172 + source 2173 + " value must have correct area id: " 2174 + areaId) 2175 .that(areaId) 2176 .isEqualTo(expectedAreaId); 2177 assertWithMessage(mPropertyName + " - areaId: " + areaId + " - source: " + source 2178 + " area ID must be in carPropertyConfig#getAreaIds()").that(Arrays.stream( 2179 carPropertyConfig.getAreaIds()).boxed().collect(Collectors.toList()).contains( 2180 areaId)).isTrue(); 2181 assertWithMessage( 2182 mPropertyName 2183 + " - areaId: " 2184 + areaId 2185 + " - source: " 2186 + source 2187 + " value must have a valid status: " 2188 + VALID_CAR_PROPERTY_VALUE_STATUSES) 2189 .that(VALID_CAR_PROPERTY_VALUE_STATUSES) 2190 .contains(status); 2191 assertWithMessage( 2192 mPropertyName 2193 + " - areaId: " 2194 + areaId 2195 + " - source: " 2196 + source 2197 + " timestamp must use the SystemClock.elapsedRealtimeNanos() time" 2198 + " base") 2199 .that(timestampNanos) 2200 .isAtLeast(0); 2201 assertWithMessage( 2202 mPropertyName 2203 + " - areaId: " 2204 + areaId 2205 + " - source: " 2206 + source 2207 + " timestamp must use the SystemClock.elapsedRealtimeNanos() time" 2208 + " base") 2209 .that(timestampNanos) 2210 .isLessThan(SystemClock.elapsedRealtimeNanos()); 2211 assertWithMessage( 2212 mPropertyName 2213 + " - areaId: " 2214 + areaId 2215 + " - source: " 2216 + source 2217 + " must return " 2218 + mPropertyType 2219 + " type value") 2220 .that(value.getClass()) 2221 .isEqualTo(mPropertyType); 2222 2223 if (mRequirePropertyValueToBeInConfigArray) { 2224 assertWithMessage( 2225 mPropertyName 2226 + " - areaId: " 2227 + areaId 2228 + " - source: " 2229 + source 2230 + " value must be listed in configArray," 2231 + " configArray:") 2232 .that(carPropertyConfig.getConfigArray()) 2233 .contains(value); 2234 } 2235 2236 if (isAtLeastU()) { 2237 List<T> supportedEnumValues = carPropertyConfig.getAreaIdConfig( 2238 areaId).getSupportedEnumValues(); 2239 if (!supportedEnumValues.isEmpty()) { 2240 if (mEnumIsBitMap) { 2241 int allValidValues = 0; 2242 for (T bitEnumValue : supportedEnumValues) { 2243 allValidValues |= ((Integer) bitEnumValue).intValue(); 2244 } 2245 assertWithMessage(mPropertyName + " - areaId: " + areaId + " - source: " 2246 + source + " value must be a combination of values listed in " 2247 + "getSupportedEnumValues()") 2248 .that(((Integer) value).intValue() & allValidValues).isEqualTo(value); 2249 } else { 2250 assertWithMessage(mPropertyName + " - areaId: " + areaId + " - source: " 2251 + source + " value must be listed in getSupportedEnumValues()").that( 2252 value).isIn(supportedEnumValues); 2253 } 2254 } 2255 } 2256 2257 T areaIdMinValue = (T) carPropertyConfig.getMinValue(areaId); 2258 T areaIdMaxValue = (T) carPropertyConfig.getMaxValue(areaId); 2259 if (areaIdMinValue != null && areaIdMaxValue != null) { 2260 assertWithMessage( 2261 "Property value: " + value + " must be between the max: " 2262 + areaIdMaxValue + " and min: " + areaIdMinValue 2263 + " values for area ID: " + Integer.toHexString(areaId)).that( 2264 verifyValueInRange( 2265 areaIdMinValue, 2266 areaIdMaxValue, 2267 (T) value)) 2268 .isTrue(); 2269 } 2270 2271 if (mVerifyErrorStates) { 2272 assertWithMessage( 2273 "When ADAS feature is enabled, " 2274 + VehiclePropertyIds.toString(mPropertyId) 2275 + " must not be set to " + ErrorState.NOT_AVAILABLE_DISABLED 2276 + " (ErrorState#NOT_AVAILABLE_DISABLED") 2277 .that((Integer) value) 2278 .isNotEqualTo(ErrorState.NOT_AVAILABLE_DISABLED); 2279 } 2280 } 2281 verifyValueInRange(T min, T max, T value)2282 private boolean verifyValueInRange(T min, T max, T value) { 2283 int propertyType = mPropertyId & VehiclePropertyType.MASK; 2284 switch (propertyType) { 2285 case VehiclePropertyType.INT32: 2286 return ((Integer) value >= (Integer) min && (Integer) value <= (Integer) max); 2287 case VehiclePropertyType.INT64: 2288 return ((Long) value >= (Long) min && (Long) value <= (Long) max); 2289 case VehiclePropertyType.FLOAT: 2290 return (((Float) value > (Float) min || valueEquals(value, min)) 2291 && ((Float) value < (Float) max || valueEquals(value, max))); 2292 default: 2293 return false; 2294 } 2295 } 2296 generateAllPossibleAreaIds(ImmutableSet<Integer> areas)2297 private static ImmutableSet<Integer> generateAllPossibleAreaIds(ImmutableSet<Integer> areas) { 2298 ImmutableSet.Builder<Integer> allPossibleAreaIdsBuilder = ImmutableSet.builder(); 2299 for (int i = 1; i <= areas.size(); i++) { 2300 allPossibleAreaIdsBuilder.addAll(Sets.combinations(areas, i).stream().map(areaCombo -> { 2301 Integer possibleAreaId = 0; 2302 for (Integer area : areaCombo) { 2303 possibleAreaId |= area; 2304 } 2305 return possibleAreaId; 2306 }).collect(Collectors.toList())); 2307 } 2308 return allPossibleAreaIdsBuilder.build(); 2309 } 2310 verifyValidAreaIdsForAreaType(ImmutableSet<Integer> allPossibleAreaIds)2311 private void verifyValidAreaIdsForAreaType(ImmutableSet<Integer> allPossibleAreaIds) { 2312 for (int areaId : getCarPropertyConfig().getAreaIds()) { 2313 assertWithMessage( 2314 mPropertyName + "'s area ID must be a valid " + areaTypeToString(mAreaType) 2315 + " area ID").that(areaId).isIn(allPossibleAreaIds); 2316 } 2317 } 2318 verifyNoAreaOverlapInAreaIds(ImmutableSet<Integer> areas)2319 private void verifyNoAreaOverlapInAreaIds(ImmutableSet<Integer> areas) { 2320 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig(); 2321 if (carPropertyConfig.getAreaIds().length < 2) { 2322 return; 2323 } 2324 ImmutableSet<Integer> areaIds = ImmutableSet.copyOf(Arrays.stream( 2325 carPropertyConfig.getAreaIds()).boxed().collect(Collectors.toList())); 2326 List<Integer> areaIdOverlapCheckResults = Sets.combinations(areaIds, 2).stream().map( 2327 areaIdPair -> { 2328 List<Integer> areaIdPairAsList = areaIdPair.stream().collect( 2329 Collectors.toList()); 2330 return areaIdPairAsList.get(0) & areaIdPairAsList.get(1); 2331 }).collect(Collectors.toList()); 2332 2333 assertWithMessage( 2334 mPropertyName + " area IDs: " + Arrays.toString(carPropertyConfig.getAreaIds()) 2335 + " must contain each area only once (e.g. no bitwise AND overlap) for " 2336 + "the area type: " + areaTypeToString(mAreaType)).that( 2337 Collections.frequency(areaIdOverlapCheckResults, 0) 2338 == areaIdOverlapCheckResults.size()).isTrue(); 2339 } 2340 2341 /** 2342 * Verifies that exceptions are thrown when caller does not have read/write permission. 2343 */ verifyPermissionNotGrantedException()2344 private void verifyPermissionNotGrantedException() { 2345 // If the client itself already has read/write permissions without adopting any permissions 2346 // from the shell, skip the test. 2347 if (hasReadPermissions(mReadPermissions) || hasWritePermissions(mWritePermissions)) { 2348 return; 2349 } 2350 2351 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig(); 2352 assertWithMessage( 2353 mPropertyName 2354 + " - property ID: " 2355 + mPropertyId 2356 + " CarPropertyConfig should not be accessible without permissions.") 2357 .that(getCarPropertyConfig(/* useCache= */ false)) 2358 .isNull(); 2359 2360 int access = carPropertyConfig.getAccess(); 2361 for (int areaId : carPropertyConfig.getAreaIds()) { 2362 if (AREA_ID_CONFIG_ACCESS_FLAG) { 2363 access = carPropertyConfig.getAreaIdConfig(areaId).getAccess(); 2364 } 2365 2366 if (access == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ 2367 || access == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE) { 2368 assertGetPropertyThrowsException( 2369 mPropertyName 2370 + " - property ID: " 2371 + mPropertyId 2372 + " - area ID: " 2373 + areaId 2374 + " should not be able to be read without permissions.", 2375 SecurityException.class, mPropertyId, areaId); 2376 } 2377 if (access == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE 2378 || access == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE) { 2379 assertThrows( 2380 mPropertyName 2381 + " - property ID: " 2382 + mPropertyId 2383 + " - area ID: " 2384 + areaId 2385 + " should not be able to be written to without permissions.", 2386 SecurityException.class, 2387 () -> mCarPropertyManager.setProperty(mPropertyType, mPropertyId, areaId, 2388 getDefaultValue(mPropertyType))); 2389 } 2390 } 2391 2392 if (access == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE) { 2393 return; 2394 } 2395 2396 // We expect a return value of false and not a SecurityException thrown. 2397 // This is because registerCallback first tries to get the CarPropertyConfig for the 2398 // property, but since no permissions have been granted it can't find the CarPropertyConfig, 2399 // so it immediately returns false. 2400 assertWithMessage( 2401 mPropertyName 2402 + " - property ID: " 2403 + mPropertyId 2404 + " should not be able to be listened to without permissions.") 2405 .that( 2406 mCarPropertyManager.registerCallback(FAKE_CALLBACK, mPropertyId, 0f)) 2407 .isFalse(); 2408 2409 if (isAtLeastV() && Flags.variableUpdateRate()) { 2410 // For the new API, if the caller does not have read and write permission, it throws 2411 // SecurityException. 2412 assertThrows( 2413 mPropertyName 2414 + " - property ID: " 2415 + mPropertyId 2416 + " should not be able to be listened to without permissions.", 2417 SecurityException.class, 2418 () -> mCarPropertyManager.subscribePropertyEvents(mPropertyId, FAKE_CALLBACK)); 2419 } 2420 } 2421 verifyHvacTemperatureValueSuggestionResponse(Float[] temperatureSuggestion)2422 private void verifyHvacTemperatureValueSuggestionResponse(Float[] temperatureSuggestion) { 2423 Float suggestedTempInCelsius = temperatureSuggestion[2]; 2424 Float suggestedTempInFahrenheit = temperatureSuggestion[3]; 2425 CarPropertyConfig<?> hvacTemperatureSetCarPropertyConfig = 2426 getCarPropertyConfig(VehiclePropertyIds.HVAC_TEMPERATURE_SET); 2427 if (hvacTemperatureSetCarPropertyConfig == null) { 2428 return; 2429 } 2430 List<Integer> hvacTemperatureSetConfigArray = 2431 hvacTemperatureSetCarPropertyConfig.getConfigArray(); 2432 if (hvacTemperatureSetConfigArray.isEmpty()) { 2433 return; 2434 } 2435 Integer minTempInCelsiusTimesTen = 2436 hvacTemperatureSetConfigArray.get(0); 2437 Integer maxTempInCelsiusTimesTen = 2438 hvacTemperatureSetConfigArray.get(1); 2439 Integer incrementInCelsiusTimesTen = 2440 hvacTemperatureSetConfigArray.get(2); 2441 verifyHvacTemperatureIsValid(suggestedTempInCelsius, minTempInCelsiusTimesTen, 2442 maxTempInCelsiusTimesTen, incrementInCelsiusTimesTen); 2443 2444 Integer minTempInFahrenheitTimesTen = 2445 hvacTemperatureSetConfigArray.get(3); 2446 Integer maxTempInFahrenheitTimesTen = 2447 hvacTemperatureSetConfigArray.get(4); 2448 Integer incrementInFahrenheitTimesTen = 2449 hvacTemperatureSetConfigArray.get(5); 2450 verifyHvacTemperatureIsValid(suggestedTempInFahrenheit, minTempInFahrenheitTimesTen, 2451 maxTempInFahrenheitTimesTen, incrementInFahrenheitTimesTen); 2452 2453 int suggestedTempInCelsiusTimesTen = (int) (suggestedTempInCelsius * 10f); 2454 int suggestedTempInFahrenheitTimesTen = (int) (suggestedTempInFahrenheit * 10f); 2455 int numIncrementsCelsius = 2456 Math.round((suggestedTempInCelsiusTimesTen - minTempInCelsiusTimesTen) 2457 / incrementInCelsiusTimesTen.floatValue()); 2458 int numIncrementsFahrenheit = 2459 Math.round((suggestedTempInFahrenheitTimesTen - minTempInFahrenheitTimesTen) 2460 / incrementInFahrenheitTimesTen.floatValue()); 2461 assertWithMessage( 2462 "The temperature in celsius must map to the same temperature in fahrenheit" 2463 + " using the HVAC_TEMPERATURE_SET config array: " 2464 + hvacTemperatureSetConfigArray) 2465 .that(numIncrementsFahrenheit) 2466 .isEqualTo(numIncrementsCelsius); 2467 } 2468 getAreaIdAccess(CarPropertyConfig<?> carPropertyConfig, int areaId)2469 private static Optional<Integer> getAreaIdAccess(CarPropertyConfig<?> carPropertyConfig, 2470 int areaId) { 2471 return AREA_ID_CONFIG_ACCESS_FLAG 2472 ? Optional.of(carPropertyConfig.getAreaIdConfig(areaId).getAccess()) 2473 : Optional.empty(); 2474 } 2475 getAreaIdAccessOrElseGlobalAccess(CarPropertyConfig<?> carPropertyConfig, int areaId)2476 private static Integer getAreaIdAccessOrElseGlobalAccess(CarPropertyConfig<?> carPropertyConfig, 2477 int areaId) { 2478 return getAreaIdAccess(carPropertyConfig, areaId).orElse(carPropertyConfig.getAccess()); 2479 } 2480 doesAreaIdAccessMatch( CarPropertyConfig<?> carPropertyConfig, int areaId, int expectedAccess)2481 private static boolean doesAreaIdAccessMatch( 2482 CarPropertyConfig<?> carPropertyConfig, int areaId, int expectedAccess) { 2483 return getAreaIdAccess(carPropertyConfig, areaId) 2484 .filter(areaIdAccess -> areaIdAccess == expectedAccess).isPresent(); 2485 } 2486 doesAreaIdAccessNotMatch( CarPropertyConfig<?> carPropertyConfig, int areaId, int access)2487 private static boolean doesAreaIdAccessNotMatch( 2488 CarPropertyConfig<?> carPropertyConfig, int areaId, int access) { 2489 return getAreaIdAccess(carPropertyConfig, areaId) 2490 .filter(areaIdAccess -> areaIdAccess != access).isPresent(); 2491 } 2492 2493 /** 2494 * Verifies that hvac temperature is valid. 2495 */ verifyHvacTemperatureIsValid(float temp, int minTempTimesTen, int maxTempTimesTen, int incrementTimesTen)2496 public static void verifyHvacTemperatureIsValid(float temp, int minTempTimesTen, 2497 int maxTempTimesTen, int incrementTimesTen) { 2498 int intTempTimesTen = (int) (temp * 10f); 2499 assertWithMessage( 2500 "The temperature value " + intTempTimesTen + " must be at least " 2501 + minTempTimesTen + " and at most " + maxTempTimesTen) 2502 .that(intTempTimesTen >= minTempTimesTen && intTempTimesTen <= maxTempTimesTen) 2503 .isTrue(); 2504 2505 int remainder = (intTempTimesTen - minTempTimesTen) % incrementTimesTen; 2506 assertWithMessage( 2507 "The temperature value " + intTempTimesTen 2508 + " is not a valid temperature value. Valid values start from " 2509 + minTempTimesTen 2510 + " and increment by " + incrementTimesTen 2511 + " until the max temperature setting of " + maxTempTimesTen) 2512 .that(remainder) 2513 .isEqualTo(0); 2514 } 2515 2516 /** 2517 * A structure containing verifier context. 2518 * 2519 * This contains VehiclePropertyVerifier members that might be useful for the verification. 2520 */ 2521 public static class VerifierContext { 2522 private final CarPropertyManager mCarPropertyManager; 2523 VerifierContext(CarPropertyManager carPropertyManager)2524 public VerifierContext(CarPropertyManager carPropertyManager) { 2525 mCarPropertyManager = carPropertyManager; 2526 } 2527 getCarPropertyManager()2528 public CarPropertyManager getCarPropertyManager() { 2529 return mCarPropertyManager; 2530 } 2531 } 2532 2533 /** 2534 * An interface for verifying the config array. 2535 */ 2536 public interface ConfigArrayVerifier { 2537 /** 2538 * Verifies the config array. Throws exception if not valid. 2539 */ verify(VerifierContext verifierContext, List<Integer> configArray)2540 void verify(VerifierContext verifierContext, List<Integer> configArray); 2541 } 2542 2543 /** 2544 * An interface for verifying the property value. 2545 */ 2546 public interface CarPropertyValueVerifier<T> { 2547 /** 2548 * Verifies the property value. Throws exception if not valid. 2549 */ verify(VerifierContext verifierContext, CarPropertyConfig<T> carPropertyConfig, int propertyId, int areaId, long timestampNanos, T value)2550 void verify(VerifierContext verifierContext, CarPropertyConfig<T> carPropertyConfig, 2551 int propertyId, int areaId, long timestampNanos, T value); 2552 } 2553 2554 /** 2555 * An interface for verifying the areaIds. 2556 */ 2557 public interface AreaIdsVerifier { 2558 /** 2559 * Verifies the areaIds. Throws exception if not valid. 2560 */ verify(VerifierContext verifierContext, int[] areaIds)2561 void verify(VerifierContext verifierContext, int[] areaIds); 2562 } 2563 2564 /** 2565 * An interface for verifying the {@link CarPropertyConfig}. 2566 */ 2567 public interface CarPropertyConfigVerifier { 2568 /** 2569 * Verifies the property config. Throws exception if not valid. 2570 */ verify(VerifierContext verifierContext, CarPropertyConfig<?> carPropertyConfig)2571 void verify(VerifierContext verifierContext, CarPropertyConfig<?> carPropertyConfig); 2572 } 2573 2574 /** 2575 * The builder class. 2576 */ 2577 public static class Builder<T> { 2578 private final int mPropertyId; 2579 private final int mAccess; 2580 private final int mAreaType; 2581 private final int mChangeMode; 2582 private final Class<T> mPropertyType; 2583 private CarPropertyManager mCarPropertyManager; 2584 private boolean mRequiredProperty = false; 2585 private Optional<ConfigArrayVerifier> mConfigArrayVerifier = Optional.empty(); 2586 private Optional<CarPropertyValueVerifier<T>> mCarPropertyValueVerifier = Optional.empty(); 2587 private Optional<AreaIdsVerifier> mAreaIdsVerifier = Optional.empty(); 2588 private Optional<CarPropertyConfigVerifier> mCarPropertyConfigVerifier = Optional.empty(); 2589 private Optional<Integer> mDependentOnPropertyId = Optional.empty(); 2590 private ImmutableSet<String> mDependentOnPropertyPermissions = ImmutableSet.of(); 2591 private ImmutableSet<Integer> mPossibleConfigArrayValues = ImmutableSet.of(); 2592 private boolean mEnumIsBitMap = false; 2593 private ImmutableSet<T> mAllPossibleEnumValues = ImmutableSet.of(); 2594 private ImmutableSet<T> mAllPossibleUnwritableValues = ImmutableSet.of(); 2595 private ImmutableSet<T> mAllPossibleUnavailableValues = ImmutableSet.of(); 2596 private boolean mRequirePropertyValueToBeInConfigArray = false; 2597 private boolean mVerifySetterWithConfigArrayValues = false; 2598 private boolean mRequireMinMaxValues = false; 2599 private boolean mRequireMinValuesToBeZero = false; 2600 private boolean mRequireZeroToBeContainedInMinMaxRanges = false; 2601 private boolean mPossiblyDependentOnHvacPowerOn = false; 2602 private boolean mVerifyErrorStates = false; 2603 private final ImmutableSet.Builder<String> mReadPermissionsBuilder = ImmutableSet.builder(); 2604 private final ImmutableList.Builder<ImmutableSet<String>> mWritePermissionsBuilder = 2605 ImmutableList.builder(); 2606 Builder(int propertyId, int access, int areaType, int changeMode, Class<T> propertyType)2607 private Builder(int propertyId, int access, int areaType, int changeMode, 2608 Class<T> propertyType) { 2609 mPropertyId = propertyId; 2610 mAccess = access; 2611 mAreaType = areaType; 2612 mChangeMode = changeMode; 2613 mPropertyType = propertyType; 2614 } 2615 getPropertyId()2616 public int getPropertyId() { 2617 return mPropertyId; 2618 } 2619 isRequired()2620 public boolean isRequired() { 2621 return mRequiredProperty; 2622 } 2623 2624 /** 2625 * Sets the car property manager. 2626 */ setCarPropertyManager(CarPropertyManager carPropertyManager)2627 public Builder<T> setCarPropertyManager(CarPropertyManager carPropertyManager) { 2628 mCarPropertyManager = carPropertyManager; 2629 return this; 2630 } 2631 2632 /** 2633 * Sets the property as required. Test will fail if the property is not supported. 2634 */ requireProperty()2635 public Builder<T> requireProperty() { 2636 mRequiredProperty = true; 2637 return this; 2638 } 2639 2640 /** 2641 * Sets the config array verifier. 2642 */ setConfigArrayVerifier(ConfigArrayVerifier configArrayVerifier)2643 public Builder<T> setConfigArrayVerifier(ConfigArrayVerifier configArrayVerifier) { 2644 mConfigArrayVerifier = Optional.of(configArrayVerifier); 2645 return this; 2646 } 2647 2648 /** 2649 * Sets the car property value verifier. 2650 */ setCarPropertyValueVerifier( CarPropertyValueVerifier<T> carPropertyValueVerifier)2651 public Builder<T> setCarPropertyValueVerifier( 2652 CarPropertyValueVerifier<T> carPropertyValueVerifier) { 2653 mCarPropertyValueVerifier = Optional.of(carPropertyValueVerifier); 2654 return this; 2655 } 2656 2657 /** 2658 * Sets the areaIds verifier. 2659 */ setAreaIdsVerifier(AreaIdsVerifier areaIdsVerifier)2660 public Builder<T> setAreaIdsVerifier(AreaIdsVerifier areaIdsVerifier) { 2661 mAreaIdsVerifier = Optional.of(areaIdsVerifier); 2662 return this; 2663 } 2664 2665 /** 2666 * Sets the car property config verifier. 2667 */ setCarPropertyConfigVerifier( CarPropertyConfigVerifier carPropertyConfigVerifier)2668 public Builder<T> setCarPropertyConfigVerifier( 2669 CarPropertyConfigVerifier carPropertyConfigVerifier) { 2670 mCarPropertyConfigVerifier = Optional.of(carPropertyConfigVerifier); 2671 return this; 2672 } 2673 2674 /** 2675 * Sets that the property is depending on other properties. 2676 */ setDependentOnProperty(Integer dependentPropertyId, ImmutableSet<String> dependentPropertyPermissions)2677 public Builder<T> setDependentOnProperty(Integer dependentPropertyId, 2678 ImmutableSet<String> dependentPropertyPermissions) { 2679 mDependentOnPropertyId = Optional.of(dependentPropertyId); 2680 mDependentOnPropertyPermissions = dependentPropertyPermissions; 2681 return this; 2682 } 2683 2684 /** 2685 * Sets the possible config array values. 2686 */ setPossibleConfigArrayValues( ImmutableSet<Integer> possibleConfigArrayValues)2687 public Builder<T> setPossibleConfigArrayValues( 2688 ImmutableSet<Integer> possibleConfigArrayValues) { 2689 mPossibleConfigArrayValues = possibleConfigArrayValues; 2690 return this; 2691 } 2692 2693 /** 2694 * Used to assert that supportedEnum values provided in config are a subset of all possible 2695 * enum values that can be set for the property. 2696 */ setBitMapEnumEnabled(boolean enabled)2697 public Builder<T> setBitMapEnumEnabled(boolean enabled) { 2698 mEnumIsBitMap = enabled; 2699 return this; 2700 } 2701 2702 /* 2703 * Used to assert that supportedEnum values provided in config are a subset of all possible 2704 * enum values that can be set for the property. If enums is defined as a bit map rather 2705 * than a regular integer, setBitMapEnumEnabled(boolean) should be used as well. 2706 */ setAllPossibleEnumValues(ImmutableSet<T> allPossibleEnumValues)2707 public Builder<T> setAllPossibleEnumValues(ImmutableSet<T> allPossibleEnumValues) { 2708 mAllPossibleEnumValues = allPossibleEnumValues; 2709 return this; 2710 } 2711 2712 /** 2713 * Used to assert that certain values that must not be allowed to be written will throw an 2714 * IllegalArgumentException when we try to write them using setProperty. 2715 */ setAllPossibleUnwritableValues( ImmutableSet<T> allPossibleUnwritableValues)2716 public Builder<T> setAllPossibleUnwritableValues( 2717 ImmutableSet<T> allPossibleUnwritableValues) { 2718 mAllPossibleUnwritableValues = allPossibleUnwritableValues; 2719 return this; 2720 } 2721 2722 /** 2723 * Used to assert that certain values that are temporarily unavailable to be written will 2724 * throw a PropertyNotAvailableException when we try to write them using setProperty. 2725 */ setAllPossibleUnavailableValues( ImmutableSet<T> allPossibleUnavailableValues)2726 public Builder<T> setAllPossibleUnavailableValues( 2727 ImmutableSet<T> allPossibleUnavailableValues) { 2728 mAllPossibleUnavailableValues = allPossibleUnavailableValues; 2729 return this; 2730 } 2731 2732 /** 2733 * Requires that the property value must be one of the value defined in the config array. 2734 */ requirePropertyValueTobeInConfigArray()2735 public Builder<T> requirePropertyValueTobeInConfigArray() { 2736 mRequirePropertyValueToBeInConfigArray = true; 2737 return this; 2738 } 2739 2740 /** 2741 * Uses the config array values to set the property value. 2742 */ verifySetterWithConfigArrayValues()2743 public Builder<T> verifySetterWithConfigArrayValues() { 2744 mVerifySetterWithConfigArrayValues = true; 2745 return this; 2746 } 2747 2748 /** 2749 * Requires minValue and maxValue to be set. 2750 */ requireMinMaxValues()2751 public Builder<T> requireMinMaxValues() { 2752 mRequireMinMaxValues = true; 2753 return this; 2754 } 2755 2756 /** 2757 * Requires minValue to be 0. 2758 */ requireMinValuesToBeZero()2759 public Builder<T> requireMinValuesToBeZero() { 2760 mRequireMinValuesToBeZero = true; 2761 return this; 2762 } 2763 2764 /** 2765 * Requires 0 to be contains within minValue and maxValue. 2766 */ requireZeroToBeContainedInMinMaxRanges()2767 public Builder<T> requireZeroToBeContainedInMinMaxRanges() { 2768 mRequireZeroToBeContainedInMinMaxRanges = true; 2769 return this; 2770 } 2771 2772 /** 2773 * Sets that the property might depend on HVAC_POEWR_ON. 2774 */ setPossiblyDependentOnHvacPowerOn()2775 public Builder<T> setPossiblyDependentOnHvacPowerOn() { 2776 mPossiblyDependentOnHvacPowerOn = true; 2777 return this; 2778 } 2779 2780 /** 2781 * Verifies if returning error state, the error state is expected. 2782 */ verifyErrorStates()2783 public Builder<T> verifyErrorStates() { 2784 mVerifyErrorStates = true; 2785 return this; 2786 } 2787 2788 /** 2789 * Adds the required read permission. 2790 */ addReadPermission(String readPermission)2791 public Builder<T> addReadPermission(String readPermission) { 2792 mReadPermissionsBuilder.add(readPermission); 2793 return this; 2794 } 2795 2796 /** 2797 * Adds a single permission that alone can be used to update the property. Any set of 2798 * permissions in {@code mWritePermissionsBuilder} can be used to set the property. 2799 * 2800 * @param writePermission a permission used to update the property 2801 */ addWritePermission(String writePermission)2802 public Builder<T> addWritePermission(String writePermission) { 2803 mWritePermissionsBuilder.add(ImmutableSet.of(writePermission)); 2804 return this; 2805 } 2806 2807 /** 2808 * Adds a set of permissions that together can be used to update the property. Any set of 2809 * permissions in {@code mWritePermissionsBuilder} can be used to set the property. 2810 * 2811 * @param writePermissionSet a set of permissions that together can be used to update the 2812 * property. 2813 */ addWritePermission(ImmutableSet<String> writePermissionSet)2814 public Builder<T> addWritePermission(ImmutableSet<String> writePermissionSet) { 2815 mWritePermissionsBuilder.add(writePermissionSet); 2816 return this; 2817 } 2818 2819 /** 2820 * Builds the verifier. 2821 */ build()2822 public VehiclePropertyVerifier<T> build() { 2823 return new VehiclePropertyVerifier<>( 2824 mCarPropertyManager, 2825 mPropertyId, 2826 mAccess, 2827 mAreaType, 2828 mChangeMode, 2829 mPropertyType, 2830 mRequiredProperty, 2831 mConfigArrayVerifier, 2832 mCarPropertyValueVerifier, 2833 mAreaIdsVerifier, 2834 mCarPropertyConfigVerifier, 2835 mDependentOnPropertyId, 2836 mDependentOnPropertyPermissions, 2837 mPossibleConfigArrayValues, 2838 mEnumIsBitMap, 2839 mAllPossibleEnumValues, 2840 mAllPossibleUnwritableValues, 2841 mAllPossibleUnavailableValues, 2842 mRequirePropertyValueToBeInConfigArray, 2843 mVerifySetterWithConfigArrayValues, 2844 mRequireMinMaxValues, 2845 mRequireMinValuesToBeZero, 2846 mRequireZeroToBeContainedInMinMaxRanges, 2847 mPossiblyDependentOnHvacPowerOn, 2848 mVerifyErrorStates, 2849 mReadPermissionsBuilder.build(), 2850 mWritePermissionsBuilder.build()); 2851 } 2852 } 2853 2854 private static class CarPropertyValueCallback implements 2855 CarPropertyManager.CarPropertyEventCallback { 2856 private final String mPropertyName; 2857 private final int[] mAreaIds; 2858 private final int mTotalCarPropertyValuesPerAreaId; 2859 private final CountDownLatch mCountDownLatch = new CountDownLatch(1); 2860 private final Object mLock = new Object(); 2861 @GuardedBy("mLock") 2862 private final SparseArray<List<CarPropertyValue<?>>> mAreaIdToCarPropertyValues = 2863 new SparseArray<>(); 2864 private final long mTimeoutMillis; 2865 CarPropertyValueCallback(String propertyName, int[] areaIds, int totalCarPropertyValuesPerAreaId, long timeoutMillis)2866 CarPropertyValueCallback(String propertyName, int[] areaIds, 2867 int totalCarPropertyValuesPerAreaId, long timeoutMillis) { 2868 mPropertyName = propertyName; 2869 mAreaIds = areaIds; 2870 mTotalCarPropertyValuesPerAreaId = totalCarPropertyValuesPerAreaId; 2871 mTimeoutMillis = timeoutMillis; 2872 synchronized (mLock) { 2873 for (int areaId : mAreaIds) { 2874 mAreaIdToCarPropertyValues.put(areaId, new ArrayList<>()); 2875 } 2876 } 2877 } 2878 getAreaIdToCarPropertyValues()2879 public SparseArray<List<CarPropertyValue<?>>> getAreaIdToCarPropertyValues() { 2880 boolean awaitSuccess = false; 2881 try { 2882 awaitSuccess = mCountDownLatch.await(mTimeoutMillis, TimeUnit.MILLISECONDS); 2883 } catch (InterruptedException e) { 2884 assertWithMessage("Waiting for onChangeEvent callback(s) for " + mPropertyName 2885 + " threw an exception: " + e).fail(); 2886 } 2887 synchronized (mLock) { 2888 assertWithMessage("Never received " + mTotalCarPropertyValuesPerAreaId 2889 + " CarPropertyValues for all " + mPropertyName + "'s areaIds: " 2890 + Arrays.toString(mAreaIds) + " before " + mTimeoutMillis + " ms timeout - " 2891 + mAreaIdToCarPropertyValues).that(awaitSuccess).isTrue(); 2892 return mAreaIdToCarPropertyValues.clone(); 2893 } 2894 } 2895 2896 @Override onChangeEvent(CarPropertyValue carPropertyValue)2897 public void onChangeEvent(CarPropertyValue carPropertyValue) { 2898 synchronized (mLock) { 2899 if (hasEnoughCarPropertyValuesForEachAreaIdLocked()) { 2900 return; 2901 } 2902 mAreaIdToCarPropertyValues.get(carPropertyValue.getAreaId()).add(carPropertyValue); 2903 if (hasEnoughCarPropertyValuesForEachAreaIdLocked()) { 2904 mCountDownLatch.countDown(); 2905 } 2906 } 2907 } 2908 2909 @GuardedBy("mLock") hasEnoughCarPropertyValuesForEachAreaIdLocked()2910 private boolean hasEnoughCarPropertyValuesForEachAreaIdLocked() { 2911 for (int areaId : mAreaIds) { 2912 List<CarPropertyValue<?>> carPropertyValues = mAreaIdToCarPropertyValues.get( 2913 areaId); 2914 if (carPropertyValues == null 2915 || carPropertyValues.size() < mTotalCarPropertyValuesPerAreaId) { 2916 return false; 2917 } 2918 } 2919 return true; 2920 } 2921 2922 @Override onErrorEvent(int propId, int zone)2923 public void onErrorEvent(int propId, int zone) { 2924 } 2925 2926 @Override onErrorEvent(int propId, int areaId, int errorCode)2927 public void onErrorEvent(int propId, int areaId, int errorCode) { 2928 } 2929 } 2930 2931 2932 private static class SetterCallback<T> implements CarPropertyManager.CarPropertyEventCallback { 2933 private final int mPropertyId; 2934 private final String mPropertyName; 2935 private final int mAreaId; 2936 private final T mExpectedSetValue; 2937 private final CountDownLatch mCountDownLatch = new CountDownLatch(1); 2938 private final long mCreationTimeNanos = SystemClock.elapsedRealtimeNanos(); 2939 private CarPropertyValue<?> mUpdatedCarPropertyValue = null; 2940 private T mReceivedValue = null; 2941 SetterCallback(int propertyId, int areaId, T expectedSetValue)2942 SetterCallback(int propertyId, int areaId, T expectedSetValue) { 2943 mPropertyId = propertyId; 2944 mPropertyName = VehiclePropertyIds.toString(propertyId); 2945 mAreaId = areaId; 2946 mExpectedSetValue = expectedSetValue; 2947 } 2948 valueToString(T value)2949 private String valueToString(T value) { 2950 if (value.getClass().isArray()) { 2951 return Arrays.toString((Object[]) value); 2952 } 2953 return value.toString(); 2954 } 2955 2956 /** 2957 * Waits at most {@code timeoutInSec} for a property event that is the result of a 2958 * {@code setProperty} request. 2959 * 2960 * <p>If {@link #onChangeEvent(CarPropertyValue)} is called, then this method will return 2961 * the {@link CarPropertyValue} if: 2962 * <ul> 2963 * <li>The property ID and area ID match {@link #mPropertyId} and {@link #mAreaId} 2964 * <li>The event is timestamped after {@link #mCreationTimeNanos} but before 2965 * {@link SystemClock#elapsedRealtimeNanos()} 2966 * <li>One of the following is true: 2967 * <ul> 2968 * <li>{@link CarPropertyValue#getStatus()} is NOT 2969 * {@link CarPropertyValue#STATUS_AVAILABLE} 2970 * <li>{@link CarPropertyValue#getStatus()} is 2971 * {@link CarPropertyValue#STATUS_AVAILABLE} and {@link CarPropertyValue#getValue()} 2972 * equals {@link #mExpectedSetValue}. 2973 * </ul> 2974 * </ul> 2975 * 2976 * <p>If {@link #onErrorEvent(int, int)} is called, then this method will return 2977 * {@code null} if: 2978 * <ul> 2979 * <li>The property ID and area ID match {@link #mPropertyId} and {@link #mAreaId} 2980 * </ul> 2981 * 2982 * <p>If {@code timeoutInSec} is reached before any of the above conditions are met or if 2983 * {@link InterruptedException} is thrown, then this method will throw an 2984 * {@code AssertionError} and fail the test. 2985 * 2986 * @param timeoutInSec maximum time in seconds to wait for an expected event 2987 * @return a valid {@link CarPropertyValue} if all {@link #onChangeEvent(CarPropertyValue)} 2988 * conditions are met, or {@code null} if all {@link #onErrorEvent(int, int)} conditions 2989 * are met. 2990 */ waitForPropertyEvent(int timeoutInSec)2991 public CarPropertyValue<?> waitForPropertyEvent(int timeoutInSec) { 2992 try { 2993 assertWithMessage( 2994 "Never received onChangeEvent(s) or onErrorEvent(s) for " + mPropertyName 2995 + " new value: " + valueToString(mExpectedSetValue) + " before" 2996 + " timeout. Received: " 2997 + (mReceivedValue == null 2998 ? "No value" 2999 : valueToString(mReceivedValue))) 3000 .that(mCountDownLatch.await(timeoutInSec, TimeUnit.SECONDS)).isTrue(); 3001 } catch (InterruptedException e) { 3002 assertWithMessage("Waiting for onChangeEvent set callback for " 3003 + mPropertyName + " threw an exception: " + e).fail(); 3004 } 3005 return mUpdatedCarPropertyValue; 3006 } 3007 3008 @Override onChangeEvent(CarPropertyValue carPropertyValue)3009 public void onChangeEvent(CarPropertyValue carPropertyValue) { 3010 // Checking whether the updated carPropertyValue is caused by the setProperty request. 3011 if (mUpdatedCarPropertyValue != null || carPropertyValue.getPropertyId() != mPropertyId 3012 || carPropertyValue.getAreaId() != mAreaId 3013 || carPropertyValue.getTimestamp() <= mCreationTimeNanos 3014 || carPropertyValue.getTimestamp() >= SystemClock.elapsedRealtimeNanos()) { 3015 return; 3016 } 3017 mReceivedValue = (T) carPropertyValue.getValue(); 3018 if (carPropertyValue.getStatus() == CarPropertyValue.STATUS_AVAILABLE 3019 && !valueEquals(mExpectedSetValue, mReceivedValue)) { 3020 return; 3021 } 3022 mUpdatedCarPropertyValue = carPropertyValue; 3023 mCountDownLatch.countDown(); 3024 } 3025 3026 @Override onErrorEvent(int propId, int areaId)3027 public void onErrorEvent(int propId, int areaId) { 3028 onErrorEvent(propId, areaId, CarPropertyManager.CAR_SET_PROPERTY_ERROR_CODE_UNKNOWN); 3029 } 3030 3031 3032 @Override onErrorEvent(int propId, int areaId, int errorCode)3033 public void onErrorEvent(int propId, int areaId, int errorCode) { 3034 if (propId != mPropertyId || areaId != mAreaId) { 3035 Log.d(TAG, "SetterCallback - Received unexpected setProperty error code: " 3036 + errorCode + " - propertyId: " + mPropertyName + " - areaId: " + areaId); 3037 return; 3038 } 3039 Log.w(TAG, "SetterCallback - Received setProperty error code: " + errorCode 3040 + " - propertyId: " + mPropertyName + " - areaId: " + areaId); 3041 mCountDownLatch.countDown(); 3042 } 3043 } 3044 valueEquals(V v1, V v2)3045 private static <V> boolean valueEquals(V v1, V v2) { 3046 return (v1 instanceof Float && floatEquals((Float) v1, (Float) v2)) 3047 || (v1 instanceof Float[] && floatArrayEquals((Float[]) v1, (Float[]) v2)) 3048 || (v1 instanceof Long[] && longArrayEquals((Long[]) v1, (Long[]) v2)) 3049 || (v1 instanceof Integer[] && integerArrayEquals((Integer[]) v1, (Integer[]) v2)) 3050 || v1.equals(v2); 3051 } 3052 floatEquals(float f1, float f2)3053 private static boolean floatEquals(float f1, float f2) { 3054 return Math.abs(f1 - f2) < FLOAT_INEQUALITY_THRESHOLD; 3055 } 3056 floatArrayEquals(Float[] f1, Float[] f2)3057 private static boolean floatArrayEquals(Float[] f1, Float[] f2) { 3058 return Arrays.equals(f1, f2); 3059 } 3060 longArrayEquals(Long[] l1, Long[] l2)3061 private static boolean longArrayEquals(Long[] l1, Long[] l2) { 3062 return Arrays.equals(l1, l2); 3063 } 3064 integerArrayEquals(Integer[] i1, Integer[] i2)3065 private static boolean integerArrayEquals(Integer[] i1, Integer[] i2) { 3066 return Arrays.equals(i1, i2); 3067 } 3068 3069 private class TestGetPropertyCallback implements GetPropertyCallback { 3070 private final CountDownLatch mCountDownLatch; 3071 private final int mGetPropertyResultsCount; 3072 private final Object mLock = new Object(); 3073 @GuardedBy("mLock") 3074 private final List<GetPropertyResult<?>> mGetPropertyResults = new ArrayList<>(); 3075 @GuardedBy("mLock") 3076 private final List<PropertyAsyncError> mPropertyAsyncErrors = new ArrayList<>(); 3077 waitForResults()3078 public void waitForResults() { 3079 try { 3080 assertWithMessage("Received " + (mGetPropertyResultsCount 3081 - mCountDownLatch.getCount()) + " onSuccess(s), expected " 3082 + mGetPropertyResultsCount + " onSuccess(s)").that(mCountDownLatch.await( 3083 5, TimeUnit.SECONDS)).isTrue(); 3084 } catch (InterruptedException e) { 3085 assertWithMessage("Waiting for onSuccess threw an exception: " + e).fail(); 3086 } 3087 } getGetPropertyResults()3088 public List<GetPropertyResult<?>> getGetPropertyResults() { 3089 synchronized (mLock) { 3090 return mGetPropertyResults; 3091 } 3092 } 3093 getPropertyAsyncErrors()3094 public List<PropertyAsyncError> getPropertyAsyncErrors() { 3095 synchronized (mLock) { 3096 return mPropertyAsyncErrors; 3097 } 3098 } 3099 3100 @Override onSuccess(GetPropertyResult getPropertyResult)3101 public void onSuccess(GetPropertyResult getPropertyResult) { 3102 synchronized (mLock) { 3103 mGetPropertyResults.add(getPropertyResult); 3104 mCountDownLatch.countDown(); 3105 } 3106 } 3107 3108 @Override onFailure(PropertyAsyncError propertyAsyncError)3109 public void onFailure(PropertyAsyncError propertyAsyncError) { 3110 synchronized (mLock) { 3111 mPropertyAsyncErrors.add(propertyAsyncError); 3112 mCountDownLatch.countDown(); 3113 } 3114 } 3115 TestGetPropertyCallback(int getPropertyResultsCount)3116 TestGetPropertyCallback(int getPropertyResultsCount) { 3117 mCountDownLatch = new CountDownLatch(getPropertyResultsCount); 3118 mGetPropertyResultsCount = getPropertyResultsCount; 3119 } 3120 } 3121 3122 private class TestSetPropertyCallback implements SetPropertyCallback { 3123 private final CountDownLatch mCountDownLatch; 3124 private final int mSetPropertyResultsCount; 3125 private final Object mLock = new Object(); 3126 @GuardedBy("mLock") 3127 private final List<SetPropertyResult> mSetPropertyResults = new ArrayList<>(); 3128 @GuardedBy("mLock") 3129 private final List<PropertyAsyncError> mPropertyAsyncErrors = new ArrayList<>(); 3130 waitForResults()3131 public void waitForResults() { 3132 try { 3133 assertWithMessage("Received " + (mSetPropertyResultsCount 3134 - mCountDownLatch.getCount()) + " onSuccess(s), expected " 3135 + mSetPropertyResultsCount + " onSuccess(s)").that(mCountDownLatch.await( 3136 5, TimeUnit.SECONDS)).isTrue(); 3137 } catch (InterruptedException e) { 3138 assertWithMessage("Waiting for onSuccess threw an exception: " + e 3139 ).fail(); 3140 } 3141 } getSetPropertyResults()3142 public List<SetPropertyResult> getSetPropertyResults() { 3143 synchronized (mLock) { 3144 return mSetPropertyResults; 3145 } 3146 } 3147 getPropertyAsyncErrors()3148 public List<PropertyAsyncError> getPropertyAsyncErrors() { 3149 synchronized (mLock) { 3150 return mPropertyAsyncErrors; 3151 } 3152 } 3153 3154 @Override onSuccess(SetPropertyResult setPropertyResult)3155 public void onSuccess(SetPropertyResult setPropertyResult) { 3156 synchronized (mLock) { 3157 mSetPropertyResults.add(setPropertyResult); 3158 mCountDownLatch.countDown(); 3159 } 3160 } 3161 3162 @Override onFailure(PropertyAsyncError propertyAsyncError)3163 public void onFailure(PropertyAsyncError propertyAsyncError) { 3164 synchronized (mLock) { 3165 mPropertyAsyncErrors.add(propertyAsyncError); 3166 mCountDownLatch.countDown(); 3167 } 3168 } 3169 TestSetPropertyCallback(int setPropertyResultsCount)3170 TestSetPropertyCallback(int setPropertyResultsCount) { 3171 mCountDownLatch = new CountDownLatch(setPropertyResultsCount); 3172 mSetPropertyResultsCount = setPropertyResultsCount; 3173 } 3174 } 3175 verifyGetPropertiesAsync()3176 private void verifyGetPropertiesAsync() { 3177 if (!isAtLeastU()) { 3178 return; 3179 } 3180 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig(); 3181 if (!AREA_ID_CONFIG_ACCESS_FLAG && carPropertyConfig.getAccess() 3182 == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE) { 3183 verifyGetPropertiesAsyncFails(carPropertyConfig.getAreaIds()[0]); 3184 return; 3185 } 3186 3187 List<GetPropertyRequest> getPropertyRequests = new ArrayList<>(); 3188 SparseIntArray requestIdToAreaIdMap = new SparseIntArray(); 3189 for (AreaIdConfig<?> areaIdConfig : carPropertyConfig.getAreaIdConfigs()) { 3190 int areaId = areaIdConfig.getAreaId(); 3191 if (doesAreaIdAccessMatch(carPropertyConfig, areaId, 3192 CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE)) { 3193 verifyGetPropertiesAsyncFails(areaId); 3194 continue; 3195 } 3196 GetPropertyRequest getPropertyRequest = mCarPropertyManager.generateGetPropertyRequest( 3197 mPropertyId, areaId); 3198 int requestId = getPropertyRequest.getRequestId(); 3199 requestIdToAreaIdMap.put(requestId, areaId); 3200 getPropertyRequests.add(getPropertyRequest); 3201 } 3202 3203 TestGetPropertyCallback testGetPropertyCallback = new TestGetPropertyCallback( 3204 requestIdToAreaIdMap.size()); 3205 mCarPropertyManager.getPropertiesAsync(getPropertyRequests, /* cancellationSignal: */ null, 3206 /* callbackExecutor: */ null, testGetPropertyCallback); 3207 testGetPropertyCallback.waitForResults(); 3208 3209 for (GetPropertyResult<?> getPropertyResult : 3210 testGetPropertyCallback.getGetPropertyResults()) { 3211 int requestId = getPropertyResult.getRequestId(); 3212 int propertyId = getPropertyResult.getPropertyId(); 3213 if (requestIdToAreaIdMap.indexOfKey(requestId) < 0) { 3214 assertWithMessage( 3215 "getPropertiesAsync received GetPropertyResult with unknown requestId: " 3216 + getPropertyResult).fail(); 3217 } 3218 Integer expectedAreaId = requestIdToAreaIdMap.get(requestId); 3219 verifyCarPropertyValue(propertyId, getPropertyResult.getAreaId(), 3220 CarPropertyValue.STATUS_AVAILABLE, getPropertyResult.getTimestampNanos(), 3221 (T) getPropertyResult.getValue(), expectedAreaId, 3222 CAR_PROPERTY_VALUE_SOURCE_CALLBACK); 3223 if (mPropertyId == VehiclePropertyIds.HVAC_TEMPERATURE_VALUE_SUGGESTION) { 3224 verifyHvacTemperatureValueSuggestionResponse( 3225 (Float[]) getPropertyResult.getValue()); 3226 } 3227 } 3228 3229 for (PropertyAsyncError propertyAsyncError : 3230 testGetPropertyCallback.getPropertyAsyncErrors()) { 3231 int requestId = propertyAsyncError.getRequestId(); 3232 if (requestIdToAreaIdMap.indexOfKey(requestId) < 0) { 3233 assertWithMessage( 3234 "getPropertiesAsync received PropertyAsyncError with unknown requestId: " 3235 + propertyAsyncError).fail(); 3236 } 3237 assertWithMessage("Received PropertyAsyncError when testing getPropertiesAsync: " 3238 + propertyAsyncError).fail(); 3239 } 3240 } 3241 verifyGetPropertiesAsyncFails(int areaId)3242 private void verifyGetPropertiesAsyncFails(int areaId) { 3243 if (!isAtLeastU()) { 3244 return; 3245 } 3246 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig(); 3247 List<GetPropertyRequest> getPropertyRequests = new ArrayList<>(); 3248 GetPropertyRequest getPropertyRequest = mCarPropertyManager.generateGetPropertyRequest( 3249 mPropertyId, areaId); 3250 getPropertyRequests.add(getPropertyRequest); 3251 TestGetPropertyCallback testGetPropertyCallback = new TestGetPropertyCallback( 3252 /* getPropertyResultsCount: */ 1); 3253 assertThrows( 3254 mPropertyName 3255 + " is a write_only property so getPropertiesAsync should throw an" 3256 + " IllegalArgumentException.", 3257 IllegalArgumentException.class, 3258 () -> mCarPropertyManager.getPropertiesAsync(getPropertyRequests, 3259 /* cancellationSignal: */ null, /* callbackExecutor: */ null, 3260 testGetPropertyCallback)); 3261 } 3262 verifySetPropertiesAsync()3263 private void verifySetPropertiesAsync() { 3264 if (!isAtLeastU()) { 3265 return; 3266 } 3267 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig(); 3268 if (!AREA_ID_CONFIG_ACCESS_FLAG && carPropertyConfig.getAccess() 3269 == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ) { 3270 verifySetPropertiesAsyncFails(carPropertyConfig.getAreaIds()[0]); 3271 return; 3272 } 3273 3274 ArrayMap<Integer, List<T>> areaIdToPossibleValuesMap = new ArrayMap<>(); 3275 // The maximum possible values count for all areaIds. 3276 int maxPossibleValuesCount = 0; 3277 3278 for (AreaIdConfig<?> areaIdConfig : carPropertyConfig.getAreaIdConfigs()) { 3279 int areaId = areaIdConfig.getAreaId(); 3280 Collection<T> possibleValues = getPossibleValues(areaId); 3281 if (possibleValues == null || possibleValues.size() == 0) { 3282 continue; 3283 } 3284 // Convert to a list so that we can access via index later. 3285 areaIdToPossibleValuesMap.put(areaId, new ArrayList<T>(possibleValues)); 3286 if (possibleValues.size() > maxPossibleValuesCount) { 3287 maxPossibleValuesCount = possibleValues.size(); 3288 } 3289 } 3290 3291 // For each possible value index, generate one async request containing all areaIds that has 3292 // possible values defined. 3293 // For example, [value0ForArea1, value0ForArea2], [value1ForArea1, value1ForArea2]. 3294 // If we run out of possible values for one areaId, just use the last possible value for 3295 // that areaId. 3296 for (int i = 0; i < maxPossibleValuesCount; i++) { 3297 SparseIntArray requestIdToAreaIdMap = new SparseIntArray(); 3298 List<SetPropertyRequest<?>> setPropertyRequests = new ArrayList<>(); 3299 for (AreaIdConfig<?> areaIdConfig : carPropertyConfig.getAreaIdConfigs()) { 3300 int areaId = areaIdConfig.getAreaId(); 3301 if (doesAreaIdAccessMatch(carPropertyConfig, areaId, 3302 CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ)) { 3303 verifySetPropertiesAsyncFails(areaId); 3304 continue; 3305 } 3306 if (!areaIdToPossibleValuesMap.containsKey(areaId)) { 3307 continue; 3308 } 3309 List<T> possibleValues = areaIdToPossibleValuesMap.get(areaId); 3310 // Always use the last possible value if we run out of possible values. 3311 int index = Math.min(i, possibleValues.size() - 1); 3312 SetPropertyRequest setPropertyRequest = 3313 mCarPropertyManager.generateSetPropertyRequest(mPropertyId, areaId, 3314 possibleValues.get(index)); 3315 if (getAreaIdAccessOrElseGlobalAccess(carPropertyConfig, areaId) 3316 == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE) { 3317 setPropertyRequest.setWaitForPropertyUpdate(false); 3318 } 3319 requestIdToAreaIdMap.put(setPropertyRequest.getRequestId(), areaId); 3320 setPropertyRequests.add(setPropertyRequest); 3321 } 3322 3323 TestSetPropertyCallback testSetPropertyCallback = new TestSetPropertyCallback( 3324 requestIdToAreaIdMap.size()); 3325 mCarPropertyManager.setPropertiesAsync(setPropertyRequests, 3326 /* cancellationSignal: */ null, /* callbackExecutor: */ null, 3327 testSetPropertyCallback); 3328 testSetPropertyCallback.waitForResults(); 3329 3330 for (SetPropertyResult setPropertyResult : 3331 testSetPropertyCallback.getSetPropertyResults()) { 3332 int requestId = setPropertyResult.getRequestId(); 3333 if (requestIdToAreaIdMap.indexOfKey(requestId) < 0) { 3334 assertWithMessage( 3335 "setPropertiesAsync received SetPropertyResult with unknown requestId: " 3336 + setPropertyResult).fail(); 3337 } 3338 assertThat(setPropertyResult.getPropertyId()).isEqualTo(mPropertyId); 3339 assertThat(setPropertyResult.getAreaId()).isEqualTo( 3340 requestIdToAreaIdMap.get(requestId)); 3341 assertThat(setPropertyResult.getUpdateTimestampNanos()).isAtLeast(0); 3342 assertThat(setPropertyResult.getUpdateTimestampNanos()).isLessThan( 3343 SystemClock.elapsedRealtimeNanos()); 3344 } 3345 3346 for (PropertyAsyncError propertyAsyncError : 3347 testSetPropertyCallback.getPropertyAsyncErrors()) { 3348 int requestId = propertyAsyncError.getRequestId(); 3349 if (requestIdToAreaIdMap.indexOfKey(requestId) < 0) { 3350 assertWithMessage("setPropertiesAsync received PropertyAsyncError with unknown " 3351 + "requestId: " + propertyAsyncError).fail(); 3352 } 3353 assertWithMessage("Received PropertyAsyncError when testing setPropertiesAsync: " 3354 + propertyAsyncError).fail(); 3355 } 3356 } 3357 } 3358 verifySetPropertiesAsyncFails(int areaId)3359 private void verifySetPropertiesAsyncFails(int areaId) { 3360 if (!isAtLeastU()) { 3361 return; 3362 } 3363 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig(); 3364 List<SetPropertyRequest<?>> setPropertyRequests = new ArrayList<>(); 3365 SetPropertyRequest setPropertyRequest = mCarPropertyManager.generateSetPropertyRequest( 3366 mPropertyId, areaId, getDefaultValue(carPropertyConfig.getPropertyType())); 3367 setPropertyRequests.add(setPropertyRequest); 3368 TestSetPropertyCallback testSetPropertyCallback = new TestSetPropertyCallback( 3369 /* setPropertyResultsCount: */ 1); 3370 assertThrows( 3371 mPropertyName 3372 + " is a read_only property so setPropertiesAsync should throw an" 3373 + " IllegalArgumentException.", 3374 IllegalArgumentException.class, 3375 () -> mCarPropertyManager.setPropertiesAsync(setPropertyRequests, 3376 /* cancellationSignal: */ null, /* callbackExecutor: */ null, 3377 testSetPropertyCallback)); 3378 } 3379 setPropertyAndWaitForChange( CarPropertyManager carPropertyManager, int propertyId, Class<U> propertyType, int areaId, U valueToSet)3380 private static <U> CarPropertyValue<U> setPropertyAndWaitForChange( 3381 CarPropertyManager carPropertyManager, int propertyId, Class<U> propertyType, 3382 int areaId, U valueToSet) { 3383 return setPropertyAndWaitForChange(carPropertyManager, propertyId, propertyType, areaId, 3384 valueToSet, valueToSet); 3385 } 3386 setPropertyAndWaitForChange( CarPropertyManager carPropertyManager, int propertyId, Class<U> propertyType, int areaId, U valueToSet, U expectedValueToGet)3387 private static <U> CarPropertyValue<U> setPropertyAndWaitForChange( 3388 CarPropertyManager carPropertyManager, int propertyId, Class<U> propertyType, 3389 int areaId, U valueToSet, U expectedValueToGet) { 3390 SetterCallback setterCallback = new SetterCallback(propertyId, areaId, expectedValueToGet); 3391 assertWithMessage("Failed to register setter callback for " + VehiclePropertyIds.toString( 3392 propertyId)).that(subscribePropertyEvents(carPropertyManager, setterCallback, 3393 propertyId, CarPropertyManager.SENSOR_RATE_FASTEST)).isTrue(); 3394 try { 3395 carPropertyManager.setProperty(propertyType, propertyId, areaId, valueToSet); 3396 } catch (PropertyNotAvailableException e) { 3397 verifyPropertyNotAvailableException(e); 3398 sExceptionClassOnSet = e.getClass(); 3399 return null; 3400 } catch (CarInternalErrorException e) { 3401 verifyInternalErrorException(e); 3402 sExceptionClassOnSet = e.getClass(); 3403 return null; 3404 } 3405 3406 CarPropertyValue<U> carPropertyValue = 3407 setterCallback.waitForPropertyEvent(SET_PROPERTY_CALLBACK_TIMEOUT_SEC); 3408 unsubscribePropertyEvents(carPropertyManager, setterCallback, propertyId); 3409 return carPropertyValue; 3410 } 3411 } 3412