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 21 import static com.google.common.truth.Truth.assertThat; 22 import static com.google.common.truth.Truth.assertWithMessage; 23 24 import static org.junit.Assert.assertThrows; 25 import static org.junit.Assume.assumeThat; 26 27 import android.car.VehicleAreaDoor; 28 import android.car.VehicleAreaMirror; 29 import android.car.VehicleAreaSeat; 30 import android.car.VehicleAreaType; 31 import android.car.VehicleAreaWheel; 32 import android.car.VehicleAreaWindow; 33 import android.car.VehiclePropertyIds; 34 import android.car.VehiclePropertyType; 35 import android.car.hardware.CarHvacFanDirection; 36 import android.car.hardware.CarPropertyConfig; 37 import android.car.hardware.CarPropertyValue; 38 import android.car.hardware.property.AreaIdConfig; 39 import android.car.hardware.property.CarInternalErrorException; 40 import android.car.hardware.property.CarPropertyManager; 41 import android.car.hardware.property.CarPropertyManager.GetPropertyCallback; 42 import android.car.hardware.property.CarPropertyManager.GetPropertyRequest; 43 import android.car.hardware.property.CarPropertyManager.GetPropertyResult; 44 import android.car.hardware.property.CarPropertyManager.PropertyAsyncError; 45 import android.car.hardware.property.CarPropertyManager.SetPropertyCallback; 46 import android.car.hardware.property.CarPropertyManager.SetPropertyRequest; 47 import android.car.hardware.property.CarPropertyManager.SetPropertyResult; 48 import android.car.hardware.property.ErrorState; 49 import android.car.hardware.property.PropertyNotAvailableAndRetryException; 50 import android.car.hardware.property.PropertyNotAvailableErrorCode; 51 import android.car.hardware.property.PropertyNotAvailableException; 52 import android.os.SystemClock; 53 import android.util.Log; 54 import android.util.SparseArray; 55 import android.util.SparseIntArray; 56 57 import androidx.annotation.Nullable; 58 59 import com.android.internal.annotations.GuardedBy; 60 61 import com.google.common.collect.ImmutableList; 62 import com.google.common.collect.ImmutableSet; 63 import com.google.common.collect.Sets; 64 65 import org.hamcrest.Matchers; 66 67 import java.util.ArrayList; 68 import java.util.Arrays; 69 import java.util.Collection; 70 import java.util.Collections; 71 import java.util.List; 72 import java.util.Optional; 73 import java.util.concurrent.CountDownLatch; 74 import java.util.concurrent.TimeUnit; 75 import java.util.concurrent.atomic.AtomicBoolean; 76 import java.util.stream.Collectors; 77 import java.util.stream.IntStream; 78 79 public class VehiclePropertyVerifier<T> { 80 private static final String TAG = VehiclePropertyVerifier.class.getSimpleName(); 81 private static final String CAR_PROPERTY_VALUE_SOURCE_GETTER = "Getter"; 82 private static final String CAR_PROPERTY_VALUE_SOURCE_CALLBACK = "Callback"; 83 private static final float FLOAT_INEQUALITY_THRESHOLD = 0.00001f; 84 private static final int VENDOR_ERROR_CODE_MINIMUM_VALUE = 0x0; 85 private static final int VENDOR_ERROR_CODE_MAXIMUM_VALUE = 0xffff; 86 private static final ImmutableSet<Integer> WHEEL_AREAS = ImmutableSet.of( 87 VehicleAreaWheel.WHEEL_LEFT_FRONT, VehicleAreaWheel.WHEEL_LEFT_REAR, 88 VehicleAreaWheel.WHEEL_RIGHT_FRONT, VehicleAreaWheel.WHEEL_RIGHT_REAR); 89 private static final ImmutableSet<Integer> ALL_POSSIBLE_WHEEL_AREA_IDS = 90 generateAllPossibleAreaIds(WHEEL_AREAS); 91 private static final ImmutableSet<Integer> WINDOW_AREAS = ImmutableSet.of( 92 VehicleAreaWindow.WINDOW_FRONT_WINDSHIELD, VehicleAreaWindow.WINDOW_REAR_WINDSHIELD, 93 VehicleAreaWindow.WINDOW_ROW_1_LEFT, VehicleAreaWindow.WINDOW_ROW_1_RIGHT, 94 VehicleAreaWindow.WINDOW_ROW_2_LEFT, VehicleAreaWindow.WINDOW_ROW_2_RIGHT, 95 VehicleAreaWindow.WINDOW_ROW_3_LEFT, VehicleAreaWindow.WINDOW_ROW_3_RIGHT, 96 VehicleAreaWindow.WINDOW_ROOF_TOP_1, VehicleAreaWindow.WINDOW_ROOF_TOP_2); 97 private static final ImmutableSet<Integer> ALL_POSSIBLE_WINDOW_AREA_IDS = 98 generateAllPossibleAreaIds(WINDOW_AREAS); 99 private static final ImmutableSet<Integer> MIRROR_AREAS = ImmutableSet.of( 100 VehicleAreaMirror.MIRROR_DRIVER_LEFT, VehicleAreaMirror.MIRROR_DRIVER_RIGHT, 101 VehicleAreaMirror.MIRROR_DRIVER_CENTER); 102 private static final ImmutableSet<Integer> ALL_POSSIBLE_MIRROR_AREA_IDS = 103 generateAllPossibleAreaIds(MIRROR_AREAS); 104 private static final ImmutableSet<Integer> SEAT_AREAS = ImmutableSet.of( 105 VehicleAreaSeat.SEAT_ROW_1_LEFT, VehicleAreaSeat.SEAT_ROW_1_CENTER, 106 VehicleAreaSeat.SEAT_ROW_1_RIGHT, VehicleAreaSeat.SEAT_ROW_2_LEFT, 107 VehicleAreaSeat.SEAT_ROW_2_CENTER, VehicleAreaSeat.SEAT_ROW_2_RIGHT, 108 VehicleAreaSeat.SEAT_ROW_3_LEFT, VehicleAreaSeat.SEAT_ROW_3_CENTER, 109 VehicleAreaSeat.SEAT_ROW_3_RIGHT); 110 private static final ImmutableSet<Integer> ALL_POSSIBLE_SEAT_AREA_IDS = 111 generateAllPossibleAreaIds(SEAT_AREAS); 112 private static final ImmutableSet<Integer> DOOR_AREAS = ImmutableSet.of( 113 VehicleAreaDoor.DOOR_ROW_1_LEFT, VehicleAreaDoor.DOOR_ROW_1_RIGHT, 114 VehicleAreaDoor.DOOR_ROW_2_LEFT, VehicleAreaDoor.DOOR_ROW_2_RIGHT, 115 VehicleAreaDoor.DOOR_ROW_3_LEFT, VehicleAreaDoor.DOOR_ROW_3_RIGHT, 116 VehicleAreaDoor.DOOR_HOOD, VehicleAreaDoor.DOOR_REAR); 117 private static final ImmutableSet<Integer> ALL_POSSIBLE_DOOR_AREA_IDS = 118 generateAllPossibleAreaIds(DOOR_AREAS); 119 private static final ImmutableSet<Integer> PROPERTY_NOT_AVAILABLE_ERROR_CODES = 120 ImmutableSet.of( 121 PropertyNotAvailableErrorCode.NOT_AVAILABLE, 122 PropertyNotAvailableErrorCode.NOT_AVAILABLE_DISABLED, 123 PropertyNotAvailableErrorCode.NOT_AVAILABLE_SPEED_LOW, 124 PropertyNotAvailableErrorCode.NOT_AVAILABLE_SPEED_HIGH, 125 PropertyNotAvailableErrorCode.NOT_AVAILABLE_POOR_VISIBILITY, 126 PropertyNotAvailableErrorCode.NOT_AVAILABLE_SAFETY); 127 128 private final CarPropertyManager mCarPropertyManager; 129 private final int mPropertyId; 130 private final String mPropertyName; 131 private final int mAccess; 132 private final int mAreaType; 133 private final int mChangeMode; 134 private final Class<T> mPropertyType; 135 private final boolean mRequiredProperty; 136 private final Optional<ConfigArrayVerifier> mConfigArrayVerifier; 137 private final Optional<CarPropertyValueVerifier<T>> mCarPropertyValueVerifier; 138 private final Optional<AreaIdsVerifier> mAreaIdsVerifier; 139 private final Optional<CarPropertyConfigVerifier> mCarPropertyConfigVerifier; 140 private final Optional<Integer> mDependentOnPropertyId; 141 private final ImmutableSet<String> mDependentOnPropertyPermissions; 142 private final ImmutableSet<Integer> mPossibleConfigArrayValues; 143 private final ImmutableSet<T> mAllPossibleEnumValues; 144 private final ImmutableSet<T> mAllPossibleUnwritableValues; 145 private final boolean mRequirePropertyValueToBeInConfigArray; 146 private final boolean mVerifySetterWithConfigArrayValues; 147 private final boolean mRequireMinMaxValues; 148 private final boolean mRequireMinValuesToBeZero; 149 private final boolean mRequireZeroToBeContainedInMinMaxRanges; 150 private final boolean mPossiblyDependentOnHvacPowerOn; 151 private final boolean mVerifyErrorStates; 152 private final ImmutableSet<String> mReadPermissions; 153 private final ImmutableList<ImmutableSet<String>> mWritePermissions; 154 155 private boolean mIsCarPropertyConfigCached; 156 private CarPropertyConfig<T> mCachedCarPropertyConfig; 157 private SparseArray<SparseArray<?>> mPropertyToAreaIdValues; 158 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, ImmutableSet<T> allPossibleEnumValues, ImmutableSet<T> allPossibleUnwritableValues, boolean requirePropertyValueToBeInConfigArray, boolean verifySetterWithConfigArrayValues, boolean requireMinMaxValues, boolean requireMinValuesToBeZero, boolean requireZeroToBeContainedInMinMaxRanges, boolean possiblyDependentOnHvacPowerOn, boolean verifyErrorStates, ImmutableSet<String> readPermissions, ImmutableList<ImmutableSet<String>> writePermissions)159 private VehiclePropertyVerifier( 160 CarPropertyManager carPropertyManager, 161 int propertyId, 162 int access, 163 int areaType, 164 int changeMode, 165 Class<T> propertyType, 166 boolean requiredProperty, 167 Optional<ConfigArrayVerifier> configArrayVerifier, 168 Optional<CarPropertyValueVerifier<T>> carPropertyValueVerifier, 169 Optional<AreaIdsVerifier> areaIdsVerifier, 170 Optional<CarPropertyConfigVerifier> carPropertyConfigVerifier, 171 Optional<Integer> dependentPropertyId, 172 ImmutableSet<String> dependentOnPropertyPermissions, 173 ImmutableSet<Integer> possibleConfigArrayValues, 174 ImmutableSet<T> allPossibleEnumValues, 175 ImmutableSet<T> allPossibleUnwritableValues, 176 boolean requirePropertyValueToBeInConfigArray, 177 boolean verifySetterWithConfigArrayValues, 178 boolean requireMinMaxValues, 179 boolean requireMinValuesToBeZero, 180 boolean requireZeroToBeContainedInMinMaxRanges, 181 boolean possiblyDependentOnHvacPowerOn, 182 boolean verifyErrorStates, 183 ImmutableSet<String> readPermissions, 184 ImmutableList<ImmutableSet<String>> writePermissions) { 185 assertWithMessage("Must set car property manager").that(carPropertyManager).isNotNull(); 186 mCarPropertyManager = carPropertyManager; 187 mPropertyId = propertyId; 188 mPropertyName = VehiclePropertyIds.toString(propertyId); 189 mAccess = access; 190 mAreaType = areaType; 191 mChangeMode = changeMode; 192 mPropertyType = propertyType; 193 mRequiredProperty = requiredProperty; 194 mConfigArrayVerifier = configArrayVerifier; 195 mCarPropertyValueVerifier = carPropertyValueVerifier; 196 mAreaIdsVerifier = areaIdsVerifier; 197 mCarPropertyConfigVerifier = carPropertyConfigVerifier; 198 mDependentOnPropertyId = dependentPropertyId; 199 mDependentOnPropertyPermissions = dependentOnPropertyPermissions; 200 mPossibleConfigArrayValues = possibleConfigArrayValues; 201 mAllPossibleEnumValues = allPossibleEnumValues; 202 mAllPossibleUnwritableValues = allPossibleUnwritableValues; 203 mRequirePropertyValueToBeInConfigArray = requirePropertyValueToBeInConfigArray; 204 mVerifySetterWithConfigArrayValues = verifySetterWithConfigArrayValues; 205 mRequireMinMaxValues = requireMinMaxValues; 206 mRequireMinValuesToBeZero = requireMinValuesToBeZero; 207 mRequireZeroToBeContainedInMinMaxRanges = requireZeroToBeContainedInMinMaxRanges; 208 mPossiblyDependentOnHvacPowerOn = possiblyDependentOnHvacPowerOn; 209 mVerifyErrorStates = verifyErrorStates; 210 mReadPermissions = readPermissions; 211 mWritePermissions = writePermissions; 212 mPropertyToAreaIdValues = new SparseArray<>(); 213 } 214 newBuilder( int propertyId, int access, int areaType, int changeMode, Class<T> propertyType, CarPropertyManager carPropertyManager)215 public static <T> Builder<T> newBuilder( 216 int propertyId, int access, int areaType, int changeMode, Class<T> propertyType, 217 CarPropertyManager carPropertyManager) { 218 return new Builder<>(propertyId, access, areaType, changeMode, propertyType, 219 carPropertyManager); 220 } 221 getPropertyName()222 public String getPropertyName() { 223 return mPropertyName; 224 } 225 226 @Nullable getDefaultValue(Class<?> clazz)227 public static <U> U getDefaultValue(Class<?> clazz) { 228 if (clazz == Boolean.class) { 229 return (U) Boolean.TRUE; 230 } 231 if (clazz == Integer.class) { 232 return (U) (Integer) 2; 233 } 234 if (clazz == Float.class) { 235 return (U) (Float) 2.f; 236 } 237 if (clazz == Long.class) { 238 return (U) (Long) 2L; 239 } 240 if (clazz == Integer[].class) { 241 return (U) new Integer[]{2}; 242 } 243 if (clazz == Float[].class) { 244 return (U) new Float[]{2.f}; 245 } 246 if (clazz == Long[].class) { 247 return (U) new Long[]{2L}; 248 } 249 if (clazz == String.class) { 250 return (U) new String("test"); 251 } 252 if (clazz == byte[].class) { 253 return (U) new byte[]{(byte) 0xbe, (byte) 0xef}; 254 } 255 return null; 256 } 257 accessToString(int access)258 private static String accessToString(int access) { 259 switch (access) { 260 case CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_NONE: 261 return "VEHICLE_PROPERTY_ACCESS_NONE"; 262 case CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ: 263 return "VEHICLE_PROPERTY_ACCESS_READ"; 264 case CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE: 265 return "VEHICLE_PROPERTY_ACCESS_WRITE"; 266 case CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE: 267 return "VEHICLE_PROPERTY_ACCESS_READ_WRITE"; 268 default: 269 return Integer.toString(access); 270 } 271 } 272 areaTypeToString(int areaType)273 private static String areaTypeToString(int areaType) { 274 switch (areaType) { 275 case VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL: 276 return "VEHICLE_AREA_TYPE_GLOBAL"; 277 case VehicleAreaType.VEHICLE_AREA_TYPE_WINDOW: 278 return "VEHICLE_AREA_TYPE_WINDOW"; 279 case VehicleAreaType.VEHICLE_AREA_TYPE_DOOR: 280 return "VEHICLE_AREA_TYPE_DOOR"; 281 case VehicleAreaType.VEHICLE_AREA_TYPE_MIRROR: 282 return "VEHICLE_AREA_TYPE_MIRROR"; 283 case VehicleAreaType.VEHICLE_AREA_TYPE_SEAT: 284 return "VEHICLE_AREA_TYPE_SEAT"; 285 case VehicleAreaType.VEHICLE_AREA_TYPE_WHEEL: 286 return "VEHICLE_AREA_TYPE_WHEEL"; 287 default: 288 return Integer.toString(areaType); 289 } 290 } 291 changeModeToString(int changeMode)292 private static String changeModeToString(int changeMode) { 293 switch (changeMode) { 294 case CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_STATIC: 295 return "VEHICLE_PROPERTY_CHANGE_MODE_STATIC"; 296 case CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE: 297 return "VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE"; 298 case CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS: 299 return "VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS"; 300 default: 301 return Integer.toString(changeMode); 302 } 303 } 304 305 /** 306 * Gets the car property config for the current property or reads from cache if already cached. 307 */ getCarPropertyConfig()308 public @Nullable CarPropertyConfig<T> getCarPropertyConfig() { 309 if (!mIsCarPropertyConfigCached) { 310 mCachedCarPropertyConfig = (CarPropertyConfig<T>) mCarPropertyManager 311 .getCarPropertyConfig(mPropertyId); 312 mIsCarPropertyConfigCached = true; 313 } 314 return mCachedCarPropertyConfig; 315 } 316 isSupported()317 public boolean isSupported() { 318 return getCarPropertyConfig() != null; 319 } 320 verify()321 public void verify() { 322 ImmutableSet.Builder<String> permissionsBuilder = ImmutableSet.<String>builder(); 323 for (ImmutableSet<String> writePermissions: mWritePermissions) { 324 permissionsBuilder.addAll(writePermissions); 325 } 326 ImmutableSet<String> allPermissions = permissionsBuilder.addAll(mReadPermissions).build(); 327 328 runWithShellPermissionIdentity( 329 () -> { 330 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig(); 331 if (carPropertyConfig == null) { 332 if (mAccess == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ || mAccess 333 == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE) { 334 assertThrows("Test does not have correct permissions granted for " 335 + mPropertyName + ". Requested permissions: " 336 + allPermissions, 337 IllegalArgumentException.class, 338 () -> mCarPropertyManager.getProperty(mPropertyId, /*areaId=*/ 339 0)); 340 } else if (mAccess == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE) { 341 assertThrows("Test does not have correct permissions granted for " 342 + mPropertyName + ". Requested permissions: " 343 + allPermissions, 344 IllegalArgumentException.class, 345 () -> mCarPropertyManager.setProperty(mPropertyType, 346 mPropertyId, /*areaId=*/ 347 0, getDefaultValue(mPropertyType))); 348 } 349 } 350 351 if (mRequiredProperty) { 352 assertWithMessage("Must support " + mPropertyName).that(isSupported()) 353 .isTrue(); 354 } else { 355 assumeThat("Skipping " + mPropertyName 356 + " CTS test because the property is not supported on " 357 + "this vehicle", 358 carPropertyConfig, Matchers.notNullValue()); 359 } 360 361 verifyCarPropertyConfig(); 362 }, allPermissions.toArray(new String[0])); 363 364 verifyPermissionNotGrantedException(); 365 verifyReadPermissions(); 366 verifyWritePermissions(); 367 } 368 verifyReadPermissions()369 private void verifyReadPermissions() { 370 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig(); 371 for (String readPermission: mReadPermissions) { 372 if (carPropertyConfig.getAccess() 373 == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE) { 374 verifyReadPermissionCannotWrite(readPermission, mWritePermissions); 375 } 376 verifyReadPermissionGivesAccessToReadApis(readPermission); 377 } 378 } 379 verifyWritePermissions()380 private void verifyWritePermissions() { 381 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig(); 382 for (ImmutableSet<String> writePermissions: mWritePermissions) { 383 if (carPropertyConfig.getAccess() != CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE) { 384 verifyWritePermissionsCannotRead(writePermissions, mReadPermissions); 385 } 386 if (carPropertyConfig.getAccess() == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ) { 387 return; 388 } 389 if (writePermissions.size() > 1) { 390 verifyIndividualWritePermissionsCannotWrite(writePermissions); 391 } 392 verifyWritePermissionsGiveAccessToWriteApis(writePermissions, mReadPermissions); 393 } 394 } 395 verifyReadPermissionCannotWrite(String readPermission, ImmutableList<ImmutableSet<String>> writePermissions)396 private void verifyReadPermissionCannotWrite(String readPermission, 397 ImmutableList<ImmutableSet<String>> writePermissions) { 398 // If the read permission is the same as the write permission and the property does not 399 // require any other write permissions we skip this permission. 400 for (ImmutableSet<String> writePermissionSet: writePermissions) { 401 if (writePermissionSet.size() == 1 && writePermissionSet.contains(readPermission)) { 402 return; 403 } 404 } 405 runWithShellPermissionIdentity( 406 () -> { 407 assertThrows( 408 mPropertyName 409 + " - property ID: " 410 + mPropertyId 411 + " should not be able to be written to without write" 412 + " permissions.", 413 SecurityException.class, 414 () -> mCarPropertyManager.setProperty(mPropertyType, mPropertyId, 415 /* areaId = */ 0, getDefaultValue(mPropertyType))); 416 }, readPermission); 417 } 418 verifyReadPermissionGivesAccessToReadApis(String readPermission)419 private void verifyReadPermissionGivesAccessToReadApis(String readPermission) { 420 try { 421 enableAdasFeatureIfAdasStateProperty(); 422 runWithShellPermissionIdentity(() -> { 423 assertThat(mCarPropertyManager.getCarPropertyConfig(mPropertyId)).isNotNull(); 424 turnOnHvacPowerIfHvacPowerDependent(); 425 verifyCarPropertyValueGetter(); 426 verifyCarPropertyValueCallback(); 427 verifyGetPropertiesAsync(); 428 }, readPermission); 429 430 if (disableAdasFeatureIfAdasStateProperty()) { 431 runWithShellPermissionIdentity(() -> { 432 verifyAdasPropertyDisabled(); 433 }, ImmutableSet.<String>builder() 434 .add(readPermission) 435 .addAll(mDependentOnPropertyPermissions) 436 .build().toArray(new String[0])); 437 } 438 } finally { 439 // Restore all property values even if test fails. 440 runWithShellPermissionIdentity(() -> { 441 restoreInitialValues(); 442 }, ImmutableSet.<String>builder() 443 .add(readPermission) 444 .addAll(mDependentOnPropertyPermissions) 445 .build().toArray(new String[0])); 446 } 447 } 448 verifyWritePermissionsCannotRead(ImmutableSet<String> writePermissions, ImmutableSet<String> allReadPermissions)449 private void verifyWritePermissionsCannotRead(ImmutableSet<String> writePermissions, 450 ImmutableSet<String> allReadPermissions) { 451 // If there is any write permission that is also a read permission we skip the permissions. 452 if (!Collections.disjoint(writePermissions, allReadPermissions)) { 453 return; 454 } 455 runWithShellPermissionIdentity( 456 () -> { 457 assertThrows( 458 mPropertyName 459 + " - property ID: " 460 + mPropertyId 461 + " should not be able to be read without read" 462 + " permissions.", 463 SecurityException.class, 464 () -> mCarPropertyManager.getProperty(mPropertyId, /* areaId = */ 0)); 465 assertThrows( 466 mPropertyName 467 + " - property ID: " 468 + mPropertyId 469 + " should not be able to be listened to without read" 470 + " permissions.", 471 SecurityException.class, 472 () -> verifyCarPropertyValueCallback()); 473 assertThrows( 474 mPropertyName 475 + " - property ID: " 476 + mPropertyId 477 + " should not be able to be read without read" 478 + " permissions.", 479 SecurityException.class, 480 () -> verifyGetPropertiesAsync()); 481 }, writePermissions.toArray(new String[0])); 482 } 483 verifyIndividualWritePermissionsCannotWrite( ImmutableSet<String> writePermissions)484 private void verifyIndividualWritePermissionsCannotWrite( 485 ImmutableSet<String> writePermissions) { 486 String writePermissionsNeededString = String.join(", ", writePermissions); 487 for (String writePermission: writePermissions) { 488 runWithShellPermissionIdentity( 489 () -> { 490 assertThat(mCarPropertyManager.getCarPropertyConfig(mPropertyId)).isNull(); 491 assertThrows( 492 mPropertyName 493 + " - property ID: " 494 + mPropertyId 495 + " should not be able to be written to without all of the" 496 + " following permissions granted: " 497 + writePermissionsNeededString, 498 SecurityException.class, 499 () -> mCarPropertyManager.setProperty(mPropertyType, mPropertyId, 500 /* areaId = */ 0, getDefaultValue(mPropertyType))); 501 }, writePermission); 502 } 503 } 504 verifyWritePermissionsGiveAccessToWriteApis(ImmutableSet<String> writePermissions, ImmutableSet<String> readPermissions)505 private void verifyWritePermissionsGiveAccessToWriteApis(ImmutableSet<String> writePermissions, 506 ImmutableSet<String> readPermissions) { 507 ImmutableSet<String> propertyPermissions = 508 ImmutableSet.<String>builder() 509 .addAll(writePermissions) 510 .addAll(readPermissions) 511 .build(); 512 513 try { 514 enableAdasFeatureIfAdasStateProperty(); 515 runWithShellPermissionIdentity(() -> { 516 turnOnHvacPowerIfHvacPowerDependent(); 517 storeCurrentValues(); 518 verifyCarPropertyValueSetter(); 519 verifySetPropertiesAsync(); 520 521 if (turnOffHvacPowerIfHvacPowerDependent()) { 522 verifySetNotAvailable(); 523 } 524 }, propertyPermissions.toArray(new String[0])); 525 526 if (disableAdasFeatureIfAdasStateProperty()) { 527 runWithShellPermissionIdentity(() -> { 528 verifyAdasPropertyDisabled(); 529 }, propertyPermissions.toArray(new String[0])); 530 } 531 } finally { 532 // Restore all property values even if test fails. 533 runWithShellPermissionIdentity(() -> { 534 restoreInitialValues(); 535 }, ImmutableSet.<String>builder() 536 .addAll(propertyPermissions) 537 .addAll(mDependentOnPropertyPermissions) 538 .build().toArray(new String[0])); 539 } 540 } 541 turnOnHvacPowerIfHvacPowerDependent()542 private void turnOnHvacPowerIfHvacPowerDependent() { 543 if (!mPossiblyDependentOnHvacPowerOn) { 544 return; 545 } 546 547 CarPropertyConfig<Boolean> hvacPowerOnCarPropertyConfig = (CarPropertyConfig<Boolean>) 548 mCarPropertyManager.getCarPropertyConfig(VehiclePropertyIds.HVAC_POWER_ON); 549 if (hvacPowerOnCarPropertyConfig == null 550 || !hvacPowerOnCarPropertyConfig.getConfigArray().contains(mPropertyId)) { 551 return; 552 } 553 554 storeCurrentValuesForProperty(hvacPowerOnCarPropertyConfig); 555 // Turn the power on for all supported HVAC area IDs. 556 setBooleanPropertyInAllAreaIds(hvacPowerOnCarPropertyConfig, /* setValue: */ Boolean.TRUE); 557 } 558 turnOffHvacPowerIfHvacPowerDependent()559 private boolean turnOffHvacPowerIfHvacPowerDependent() { 560 if (!mPossiblyDependentOnHvacPowerOn) { 561 return false; 562 } 563 564 CarPropertyConfig<Boolean> hvacPowerOnCarPropertyConfig = (CarPropertyConfig<Boolean>) 565 mCarPropertyManager.getCarPropertyConfig(VehiclePropertyIds.HVAC_POWER_ON); 566 if (hvacPowerOnCarPropertyConfig == null 567 || !hvacPowerOnCarPropertyConfig.getConfigArray().contains(mPropertyId)) { 568 return false; 569 } 570 571 // Turn the power off for all supported HVAC area IDs. 572 setBooleanPropertyInAllAreaIds(hvacPowerOnCarPropertyConfig, /* setValue: */ Boolean.FALSE); 573 return true; 574 } 575 enableAdasFeatureIfAdasStateProperty()576 private void enableAdasFeatureIfAdasStateProperty() { 577 if (mDependentOnPropertyId.isEmpty()) { 578 return; 579 } 580 581 runWithShellPermissionIdentity(() -> { 582 int adasEnabledPropertyId = mDependentOnPropertyId.get(); 583 CarPropertyConfig<Boolean> adasEnabledCarPropertyConfig = (CarPropertyConfig<Boolean>) 584 mCarPropertyManager.getCarPropertyConfig(adasEnabledPropertyId); 585 586 if (adasEnabledCarPropertyConfig == null || adasEnabledCarPropertyConfig.getAccess() 587 == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ) { 588 Log.w(TAG, "Cannot enable " + VehiclePropertyIds.toString(adasEnabledPropertyId) 589 + " for testing " + VehiclePropertyIds.toString(mPropertyId) 590 + " because property is either not implemented or READ only." 591 + " Manually enable if it's not already enabled."); 592 return; 593 } 594 595 storeCurrentValuesForProperty(adasEnabledCarPropertyConfig); 596 // Enable ADAS feature in all supported area IDs. 597 setBooleanPropertyInAllAreaIds(adasEnabledCarPropertyConfig, 598 /* setValue: */ Boolean.TRUE); 599 }, mDependentOnPropertyPermissions.toArray(new String[0])); 600 } 601 disableAdasFeatureIfAdasStateProperty()602 private boolean disableAdasFeatureIfAdasStateProperty() { 603 if (mDependentOnPropertyId.isEmpty()) { 604 return false; 605 } 606 607 AtomicBoolean isDisabled = new AtomicBoolean(false); 608 runWithShellPermissionIdentity(() -> { 609 int adasEnabledPropertyId = mDependentOnPropertyId.get(); 610 CarPropertyConfig<Boolean> adasEnabledCarPropertyConfig = (CarPropertyConfig<Boolean>) 611 mCarPropertyManager.getCarPropertyConfig(adasEnabledPropertyId); 612 613 if (adasEnabledCarPropertyConfig == null || adasEnabledCarPropertyConfig.getAccess() 614 == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ) { 615 return; 616 } 617 618 // Disable ADAS feature in all supported area IDs. 619 setBooleanPropertyInAllAreaIds(adasEnabledCarPropertyConfig, 620 /* setValue: */ Boolean.FALSE); 621 isDisabled.set(true); 622 }, mDependentOnPropertyPermissions.toArray(new String[0])); 623 return isDisabled.get(); 624 } 625 626 /** 627 * Stores the property's current values for all areas so that they can be restored later. 628 */ storeCurrentValues()629 public void storeCurrentValues() { 630 storeCurrentValuesForProperty(getCarPropertyConfig()); 631 } 632 storeCurrentValuesForProperty(CarPropertyConfig<U> carPropertyConfig)633 private <U> void storeCurrentValuesForProperty(CarPropertyConfig<U> carPropertyConfig) { 634 SparseArray<U> areaIdToInitialValue = 635 getInitialValuesByAreaId(carPropertyConfig, mCarPropertyManager); 636 if (areaIdToInitialValue == null) { 637 return; 638 } 639 mPropertyToAreaIdValues.put(carPropertyConfig.getPropertyId(), areaIdToInitialValue); 640 } 641 642 /** 643 * Restore the property's and dependent properties values to original values stored by previous 644 * {@link #storeCurrentValues}. 645 * 646 * Do nothing if no stored current values are available. 647 */ restoreInitialValues()648 public <U> void restoreInitialValues() { 649 for (int i = 0; i < mPropertyToAreaIdValues.size(); i++) { 650 int propertyId = mPropertyToAreaIdValues.keyAt(i); 651 CarPropertyConfig<U> carPropertyConfig = (CarPropertyConfig<U>) 652 mCarPropertyManager.getCarPropertyConfig(propertyId); 653 SparseArray<U> areaIdToInitialValue = (SparseArray<U>) 654 mPropertyToAreaIdValues.get(propertyId); 655 656 if (areaIdToInitialValue == null || carPropertyConfig == null) { 657 Log.w(TAG, "No stored values for " + VehiclePropertyIds.toString(propertyId) 658 + " to restore to, ignore"); 659 return; 660 } 661 662 restoreInitialValuesByAreaId(carPropertyConfig, mCarPropertyManager, 663 areaIdToInitialValue); 664 } 665 } 666 667 // Get a map storing the property's area Ids to the initial values. 668 @Nullable getInitialValuesByAreaId( CarPropertyConfig<U> carPropertyConfig, CarPropertyManager carPropertyManager)669 private static <U> SparseArray<U> getInitialValuesByAreaId( 670 CarPropertyConfig<U> carPropertyConfig, CarPropertyManager carPropertyManager) { 671 if (carPropertyConfig.getAccess() != CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE) { 672 return null; 673 } 674 SparseArray<U> areaIdToInitialValue = new SparseArray<U>(); 675 int propertyId = carPropertyConfig.getPropertyId(); 676 String propertyName = VehiclePropertyIds.toString(propertyId); 677 for (int areaId : carPropertyConfig.getAreaIds()) { 678 CarPropertyValue<U> carPropertyValue = null; 679 try { 680 carPropertyValue = carPropertyManager.getProperty(propertyId, areaId); 681 } catch (PropertyNotAvailableAndRetryException | PropertyNotAvailableException 682 | CarInternalErrorException e) { 683 Log.w(TAG, "Failed to get property:" + propertyName + " at area ID: " + areaId 684 + " to save initial car property value. Error: " + e); 685 continue; 686 } 687 if (carPropertyValue == null) { 688 Log.w(TAG, "Failed to get property:" + propertyName + " at area ID: " + areaId 689 + " to save initial car property value."); 690 continue; 691 } 692 areaIdToInitialValue.put(areaId, (U) carPropertyValue.getValue()); 693 } 694 return areaIdToInitialValue; 695 } 696 697 /** 698 * Set boolean property to a desired value in all supported area IDs. 699 */ setBooleanPropertyInAllAreaIds(CarPropertyConfig<Boolean> booleanCarPropertyConfig, Boolean setValue)700 private void setBooleanPropertyInAllAreaIds(CarPropertyConfig<Boolean> booleanCarPropertyConfig, 701 Boolean setValue) { 702 int propertyId = booleanCarPropertyConfig.getPropertyId(); 703 for (int areaId : booleanCarPropertyConfig.getAreaIds()) { 704 if (mCarPropertyManager.getBooleanProperty(propertyId, areaId) == setValue) { 705 continue; 706 } 707 CarPropertyValue<Boolean> carPropertyValue = setPropertyAndWaitForChange( 708 mCarPropertyManager, propertyId, Boolean.class, areaId, setValue); 709 assertWithMessage( 710 VehiclePropertyIds.toString(propertyId) 711 + " carPropertyValue is null for area id: " + areaId) 712 .that(carPropertyValue).isNotNull(); 713 } 714 } 715 716 // Restore the initial values of the property provided by {@code areaIdToInitialValue}. restoreInitialValuesByAreaId(CarPropertyConfig<U> carPropertyConfig, CarPropertyManager carPropertyManager, SparseArray<U> areaIdToInitialValue)717 private static <U> void restoreInitialValuesByAreaId(CarPropertyConfig<U> carPropertyConfig, 718 CarPropertyManager carPropertyManager, SparseArray<U> areaIdToInitialValue) { 719 int propertyId = carPropertyConfig.getPropertyId(); 720 String propertyName = VehiclePropertyIds.toString(propertyId); 721 for (int i = 0; i < areaIdToInitialValue.size(); i++) { 722 int areaId = areaIdToInitialValue.keyAt(i); 723 U originalValue = areaIdToInitialValue.valueAt(i); 724 CarPropertyValue<U> currentCarPropertyValue = null; 725 try { 726 currentCarPropertyValue = carPropertyManager.getProperty(propertyId, areaId); 727 } catch (PropertyNotAvailableAndRetryException | PropertyNotAvailableException 728 | CarInternalErrorException e) { 729 Log.w(TAG, "Failed to get property:" + propertyName + " at area ID: " + areaId 730 + " to restore initial car property value. Error: " + e); 731 continue; 732 } 733 if (currentCarPropertyValue == null) { 734 Log.w(TAG, "Failed to get property:" + propertyName + " at area ID: " + areaId 735 + " to restore initial car property value."); 736 continue; 737 } 738 U currentValue = (U) currentCarPropertyValue.getValue(); 739 if (valueEquals(originalValue, currentValue)) { 740 continue; 741 } 742 CarPropertyValue<U> carPropertyValue = setPropertyAndWaitForChange(carPropertyManager, 743 propertyId, carPropertyConfig.getPropertyType(), areaId, originalValue); 744 assertWithMessage( 745 "Failed to restore car property value for property: " + propertyName 746 + " at area ID: " + areaId + " to its original value: " + originalValue 747 + ", current value: " + currentValue) 748 .that(carPropertyValue).isNotNull(); 749 } 750 } 751 752 /** 753 * Gets the possible values that could be set to. 754 * 755 * The values returned here must not cause {@code IllegalArgumentException} for set. 756 * 757 * Returns {@code null} or empty array if we don't know possible values. 758 */ getPossibleValues(int areaId)759 public @Nullable Collection<T> getPossibleValues(int areaId) { 760 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig(); 761 if (Boolean.class.equals(carPropertyConfig.getPropertyType())) { 762 return (List<T>) List.of(Boolean.TRUE, Boolean.FALSE); 763 } else if (Integer.class.equals(carPropertyConfig.getPropertyType())) { 764 return (List<T>) getPossibleIntegerValues(areaId); 765 } else if (Float.class.equals(carPropertyConfig.getPropertyType())) { 766 return getPossibleFloatValues(); 767 } 768 return null; 769 } 770 771 /** 772 * Gets the possible values for an integer property. 773 */ getPossibleIntegerValues(int areaId)774 private List<Integer> getPossibleIntegerValues(int areaId) { 775 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig(); 776 List<Integer> possibleValues = new ArrayList<>(); 777 if (mPropertyId == VehiclePropertyIds.HVAC_FAN_DIRECTION) { 778 int[] availableHvacFanDirections = mCarPropertyManager.getIntArrayProperty( 779 VehiclePropertyIds.HVAC_FAN_DIRECTION_AVAILABLE, areaId); 780 for (int i = 0; i < availableHvacFanDirections.length; i++) { 781 if (availableHvacFanDirections[i] != CarHvacFanDirection.UNKNOWN) { 782 possibleValues.add(availableHvacFanDirections[i]); 783 } 784 } 785 return possibleValues; 786 } 787 if (mVerifySetterWithConfigArrayValues) { 788 for (Integer value : carPropertyConfig.getConfigArray()) { 789 possibleValues.add(value); 790 } 791 return possibleValues; 792 } 793 794 if (!mAllPossibleEnumValues.isEmpty()) { 795 AreaIdConfig areaIdConfig = carPropertyConfig.getAreaIdConfig(areaId); 796 for (Integer value : (List<Integer>) areaIdConfig.getSupportedEnumValues()) { 797 if (mAllPossibleUnwritableValues.isEmpty() 798 || !mAllPossibleUnwritableValues.contains(value)) { 799 possibleValues.add(value); 800 } 801 } 802 } else { 803 Integer minValue = (Integer) carPropertyConfig.getMinValue(areaId); 804 Integer maxValue = (Integer) carPropertyConfig.getMaxValue(areaId); 805 if (minValue != null && maxValue != null) { 806 List<Integer> valuesToSet = IntStream.rangeClosed( 807 minValue.intValue(), maxValue.intValue()).boxed().collect( 808 Collectors.toList()); 809 for (int i = 0; i < valuesToSet.size(); i++) { 810 possibleValues.add(valuesToSet.get(i)); 811 } 812 } 813 814 } 815 return possibleValues; 816 } 817 818 /** 819 * Gets the possible values for an float property. 820 */ getPossibleFloatValues()821 private Collection<T> getPossibleFloatValues() { 822 if (mPropertyId != VehiclePropertyIds.HVAC_TEMPERATURE_SET) { 823 return new ArrayList<>(); 824 } 825 List<Integer> hvacTempSetConfigArray = getCarPropertyConfig().getConfigArray(); 826 ImmutableSet.Builder<Float> possibleHvacTempSetValuesBuilder = ImmutableSet.builder(); 827 // For HVAC_TEMPERATURE_SET, the configArray specifies the supported temperature values 828 // for the property. configArray[0] is the lower bound of the supported temperatures in 829 // Celsius. configArray[1] is the upper bound of the supported temperatures in Celsius. 830 // configArray[2] is the supported temperature increment between the two bounds. All 831 // configArray values are Celsius*10 since the configArray is List<Integer> but 832 // HVAC_TEMPERATURE_SET is a Float type property. 833 for (int possibleHvacTempSetValue = hvacTempSetConfigArray.get(0); 834 possibleHvacTempSetValue <= hvacTempSetConfigArray.get(1); 835 possibleHvacTempSetValue += hvacTempSetConfigArray.get(2)) { 836 possibleHvacTempSetValuesBuilder.add((float) possibleHvacTempSetValue / 10.0f); 837 } 838 return (Collection<T>) possibleHvacTempSetValuesBuilder.build(); 839 } 840 verifyCarPropertyValueSetter()841 private void verifyCarPropertyValueSetter() { 842 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig(); 843 if (carPropertyConfig.getAccess() == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ) { 844 verifySetPropertyFails(); 845 return; 846 } 847 if (Boolean.class.equals(carPropertyConfig.getPropertyType())) { 848 verifyBooleanPropertySetter(); 849 } else if (Integer.class.equals(carPropertyConfig.getPropertyType())) { 850 verifyIntegerPropertySetter(); 851 } else if (Float.class.equals(carPropertyConfig.getPropertyType())) { 852 verifyFloatPropertySetter(); 853 } else if (mPropertyId == VehiclePropertyIds.HVAC_TEMPERATURE_VALUE_SUGGESTION) { 854 verifyHvacTemperatureValueSuggestionSetter(); 855 } 856 } 857 verifySetPropertyFails()858 private void verifySetPropertyFails() { 859 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig(); 860 assertThrows( 861 mPropertyName 862 + " is a read_only property so setProperty should throw an" 863 + " IllegalArgumentException.", 864 IllegalArgumentException.class, 865 () -> mCarPropertyManager.setProperty(mPropertyType, mPropertyId, 866 carPropertyConfig.getAreaIds()[0], getDefaultValue(mPropertyType))); 867 } 868 verifyBooleanPropertySetter()869 private void verifyBooleanPropertySetter() { 870 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig(); 871 for (int areaId : carPropertyConfig.getAreaIds()) { 872 for (Boolean valueToSet: List.of(Boolean.TRUE, Boolean.FALSE)) { 873 verifySetProperty(areaId, (T) valueToSet); 874 } 875 } 876 } 877 878 verifyIntegerPropertySetter()879 private void verifyIntegerPropertySetter() { 880 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig(); 881 for (int areaId : carPropertyConfig.getAreaIds()) { 882 for (Integer valueToSet : getPossibleIntegerValues(areaId)) { 883 verifySetProperty(areaId, (T) valueToSet); 884 } 885 } 886 if (!mAllPossibleEnumValues.isEmpty()) { 887 for (AreaIdConfig<?> areaIdConfig : carPropertyConfig.getAreaIdConfigs()) { 888 for (T valueToSet : (List<T>) areaIdConfig.getSupportedEnumValues()) { 889 if (!mAllPossibleUnwritableValues.isEmpty() 890 && mAllPossibleUnwritableValues.contains(valueToSet)) { 891 assertThrows("Trying to set an unwritable value: " + valueToSet 892 + " to property: " + mPropertyId + " should throw an " 893 + "IllegalArgumentException", 894 IllegalArgumentException.class, 895 () -> setPropertyAndWaitForChange( 896 mCarPropertyManager, mPropertyId, 897 carPropertyConfig.getPropertyType(), 898 areaIdConfig.getAreaId(), valueToSet)); 899 } 900 } 901 } 902 } 903 } 904 verifyFloatPropertySetter()905 private void verifyFloatPropertySetter() { 906 Collection<T> possibleValues = getPossibleFloatValues(); 907 if (!possibleValues.isEmpty()) { 908 for (T valueToSet : possibleValues) { 909 for (int areaId : getCarPropertyConfig().getAreaIds()) { 910 verifySetProperty(areaId, valueToSet); 911 } 912 } 913 } 914 } 915 verifySetProperty(int areaId, T valueToSet)916 private void verifySetProperty(int areaId, T valueToSet) { 917 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig(); 918 if (carPropertyConfig.getAccess() == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE) { 919 Log.w(TAG, "Property: " + mPropertyName + " will be altered during the test and it is" 920 + " not possible to restore."); 921 verifySetPropertyOkayOrThrowExpectedExceptions(areaId, valueToSet); 922 return; 923 } 924 CarPropertyValue<T> currentCarPropertyValue = mCarPropertyManager.getProperty(mPropertyId, 925 areaId); 926 verifyCarPropertyValue(currentCarPropertyValue, areaId, CAR_PROPERTY_VALUE_SOURCE_GETTER); 927 if (valueEquals(valueToSet, currentCarPropertyValue.getValue())) { 928 return; 929 } 930 CarPropertyValue<T> updatedCarPropertyValue = setPropertyAndWaitForChange( 931 mCarPropertyManager, mPropertyId, carPropertyConfig.getPropertyType(), areaId, 932 valueToSet); 933 verifyCarPropertyValue(updatedCarPropertyValue, areaId, CAR_PROPERTY_VALUE_SOURCE_CALLBACK); 934 } 935 verifyHvacTemperatureValueSuggestionSetter()936 private void verifyHvacTemperatureValueSuggestionSetter() { 937 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig(); 938 CarPropertyConfig<?> hvacTemperatureSetCarPropertyConfig = 939 mCarPropertyManager.getCarPropertyConfig(VehiclePropertyIds.HVAC_TEMPERATURE_SET); 940 if (hvacTemperatureSetCarPropertyConfig == null) { 941 return; 942 } 943 List<Integer> hvacTemperatureSetConfigArray = 944 hvacTemperatureSetCarPropertyConfig.getConfigArray(); 945 float minTempInCelsius = hvacTemperatureSetConfigArray.get(0).floatValue() / 10f; 946 float minTempInFahrenheit = hvacTemperatureSetConfigArray.get(3).floatValue() / 10f; 947 948 Float[] temperatureRequest = new Float[] { 949 /* requestedValue = */ minTempInCelsius, 950 /* units = */ (float) 0x30, // VehicleUnit#CELSIUS 951 /* suggestedValueInCelsius = */ 0f, 952 /* suggestedValueInFahrenheit = */ 0f 953 }; 954 Float[] expectedTemperatureResponse = new Float[] { 955 /* requestedValue = */ minTempInCelsius, 956 /* units = */ (float) 0x30, // VehicleUnit#CELSIUS 957 /* suggestedValueInCelsius = */ minTempInCelsius, 958 /* suggestedValueInFahrenheit = */ minTempInFahrenheit 959 }; 960 for (int areaId: carPropertyConfig.getAreaIds()) { 961 CarPropertyValue<Float[]> updatedCarPropertyValue = setPropertyAndWaitForChange( 962 mCarPropertyManager, mPropertyId, Float[].class, areaId, 963 temperatureRequest, expectedTemperatureResponse); 964 verifyCarPropertyValue(updatedCarPropertyValue, areaId, 965 CAR_PROPERTY_VALUE_SOURCE_CALLBACK); 966 verifyHvacTemperatureValueSuggestionResponse(updatedCarPropertyValue.getValue()); 967 } 968 } 969 verifySetPropertyOkayOrThrowExpectedExceptions(int areaId, T valueToSet)970 private void verifySetPropertyOkayOrThrowExpectedExceptions(int areaId, T valueToSet) { 971 try { 972 mCarPropertyManager.setProperty(mPropertyType, mPropertyId, areaId, valueToSet); 973 } catch (PropertyNotAvailableAndRetryException e) { 974 } catch (PropertyNotAvailableException e) { 975 verifyPropertyNotAvailableException(e); 976 } catch (CarInternalErrorException e) { 977 verifyInternalErrorException(e); 978 } catch (Exception e) { 979 assertWithMessage("Unexpected exception thrown when trying to setProperty on " 980 + mPropertyName + ": " + e).fail(); 981 } 982 } 983 verifySetNotAvailable()984 private void verifySetNotAvailable() { 985 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig(); 986 if (carPropertyConfig.getAccess() != CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE) { 987 return; 988 } 989 for (int areaId : carPropertyConfig.getAreaIds()) { 990 CarPropertyValue<T> currentValue = null; 991 try { 992 // getProperty may/may not throw exception when the property is not available. 993 currentValue = mCarPropertyManager.getProperty(mPropertyId, areaId); 994 T valueToSet = getDefaultValue(mPropertyType); 995 if (valueToSet == null) { 996 assertWithMessage("Testing mixed type property is not supported").fail(); 997 } 998 verifySetProperty(areaId, valueToSet); 999 } catch (Exception e) { 1000 // In normal cases, this should throw PropertyNotAvailableException. 1001 // In rare cases, the value we are setting is the same as the current value, 1002 // which makes the set operation a no-op. So it is possible that no exception 1003 // is thrown here. 1004 // It is also possible that this may throw IllegalArgumentException if the value to 1005 // set is not valid. 1006 assertWithMessage( 1007 "Setting property " + mPropertyName + " when it's not available" 1008 + " should throw either PropertyNotAvailableException or" 1009 + " IllegalArgumentException.") 1010 .that(e.getClass()) 1011 .isAnyOf(PropertyNotAvailableException.class, 1012 IllegalArgumentException.class); 1013 } 1014 if (currentValue == null) { 1015 // If the property is not available for getting, continue. 1016 continue; 1017 } 1018 CarPropertyValue<T> newValue = mCarPropertyManager.getProperty(mPropertyId, areaId); 1019 assertWithMessage( 1020 "Setting property " + mPropertyName + " while power is off or required" 1021 + " property is disabled must have no effect.") 1022 .that(newValue.getValue()) 1023 .isEqualTo(currentValue.getValue()); 1024 } 1025 } 1026 verifyAdasPropertyDisabled()1027 private void verifyAdasPropertyDisabled() { 1028 if (!mVerifyErrorStates) { 1029 verifySetNotAvailable(); 1030 return; 1031 } 1032 1033 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig(); 1034 if (carPropertyConfig.getAccess() == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE) { 1035 return; 1036 } 1037 1038 for (int areaId : carPropertyConfig.getAreaIds()) { 1039 Integer adasState = mCarPropertyManager.getIntProperty(mPropertyId, areaId); 1040 assertWithMessage( 1041 "When ADAS feature is disabled, " 1042 + VehiclePropertyIds.toString(mPropertyId) 1043 + " must be set to " + ErrorState.NOT_AVAILABLE_DISABLED 1044 + " (ErrorState.NOT_AVAILABLE_DISABLED).") 1045 .that(adasState) 1046 .isEqualTo(ErrorState.NOT_AVAILABLE_DISABLED); 1047 } 1048 } 1049 getUpdatesPerAreaId(int changeMode)1050 private static int getUpdatesPerAreaId(int changeMode) { 1051 return changeMode != CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS 1052 ? 1 : 2; 1053 } 1054 getRegisterCallbackTimeoutMillis(int changeMode, float minSampleRate)1055 private static long getRegisterCallbackTimeoutMillis(int changeMode, float minSampleRate) { 1056 long timeoutMillis = 1500; 1057 if (changeMode == CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS) { 1058 float secondsToMillis = 1_000; 1059 long bufferMillis = 1_000; // 1 second 1060 timeoutMillis = ((long) ((1.0f / minSampleRate) * secondsToMillis 1061 * getUpdatesPerAreaId(changeMode))) + bufferMillis; 1062 } 1063 return timeoutMillis; 1064 } 1065 verifyCarPropertyValueCallback()1066 private void verifyCarPropertyValueCallback() { 1067 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig(); 1068 if (carPropertyConfig.getAccess() == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE) { 1069 verifyCallbackFails(); 1070 return; 1071 } 1072 int updatesPerAreaId = getUpdatesPerAreaId(mChangeMode); 1073 long timeoutMillis = getRegisterCallbackTimeoutMillis(mChangeMode, 1074 carPropertyConfig.getMinSampleRate()); 1075 1076 CarPropertyValueCallback carPropertyValueCallback = new CarPropertyValueCallback( 1077 mPropertyName, carPropertyConfig.getAreaIds(), updatesPerAreaId, timeoutMillis); 1078 assertWithMessage("Failed to register callback for " + mPropertyName) 1079 .that( 1080 mCarPropertyManager.registerCallback(carPropertyValueCallback, mPropertyId, 1081 carPropertyConfig.getMaxSampleRate())) 1082 .isTrue(); 1083 SparseArray<List<CarPropertyValue<?>>> areaIdToCarPropertyValues = 1084 carPropertyValueCallback.getAreaIdToCarPropertyValues(); 1085 mCarPropertyManager.unregisterCallback(carPropertyValueCallback, mPropertyId); 1086 1087 for (int areaId : carPropertyConfig.getAreaIds()) { 1088 List<CarPropertyValue<?>> carPropertyValues = areaIdToCarPropertyValues.get(areaId); 1089 assertWithMessage( 1090 mPropertyName + " callback value list is null for area ID: " + areaId).that( 1091 carPropertyValues).isNotNull(); 1092 assertWithMessage(mPropertyName + " callback values did not receive " + updatesPerAreaId 1093 + " updates for area ID: " + areaId).that(carPropertyValues.size()).isAtLeast( 1094 updatesPerAreaId); 1095 for (CarPropertyValue<?> carPropertyValue : carPropertyValues) { 1096 verifyCarPropertyValue(carPropertyValue, carPropertyValue.getAreaId(), 1097 CAR_PROPERTY_VALUE_SOURCE_CALLBACK); 1098 if (mPropertyId == VehiclePropertyIds.HVAC_TEMPERATURE_VALUE_SUGGESTION) { 1099 verifyHvacTemperatureValueSuggestionResponse( 1100 (Float[]) carPropertyValue.getValue()); 1101 } 1102 } 1103 } 1104 } 1105 verifyCallbackFails()1106 private void verifyCallbackFails() { 1107 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig(); 1108 int updatesPerAreaId = getUpdatesPerAreaId(mChangeMode); 1109 long timeoutMillis = getRegisterCallbackTimeoutMillis(mChangeMode, 1110 carPropertyConfig.getMinSampleRate()); 1111 1112 CarPropertyValueCallback carPropertyValueCallback = new CarPropertyValueCallback( 1113 mPropertyName, carPropertyConfig.getAreaIds(), updatesPerAreaId, timeoutMillis); 1114 assertThrows( 1115 mPropertyName 1116 + " is a write_only property so registerCallback should throw an" 1117 + " IllegalArgumentException.", 1118 IllegalArgumentException.class, 1119 () -> mCarPropertyManager.registerCallback(carPropertyValueCallback, mPropertyId, 1120 carPropertyConfig.getMaxSampleRate())); 1121 } 1122 verifyCarPropertyConfig()1123 private void verifyCarPropertyConfig() { 1124 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig(); 1125 assertWithMessage(mPropertyName + " CarPropertyConfig must have correct property ID") 1126 .that(carPropertyConfig.getPropertyId()) 1127 .isEqualTo(mPropertyId); 1128 int access = carPropertyConfig.getAccess(); 1129 if (mAccess == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE) { 1130 assertWithMessage( 1131 mPropertyName 1132 + " must be " 1133 + accessToString(CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ) 1134 + " or " 1135 + accessToString( 1136 CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE)) 1137 .that(access) 1138 .isIn( 1139 ImmutableSet.of( 1140 CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ, 1141 CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE)); 1142 } else { 1143 assertWithMessage(mPropertyName + " must be " + accessToString(mAccess)) 1144 .that(access) 1145 .isEqualTo(mAccess); 1146 } 1147 assertWithMessage(mPropertyName + " must be " + areaTypeToString(mAreaType)) 1148 .that(carPropertyConfig.getAreaType()) 1149 .isEqualTo(mAreaType); 1150 assertWithMessage(mPropertyName + " must be " + changeModeToString(mChangeMode)) 1151 .that(carPropertyConfig.getChangeMode()) 1152 .isEqualTo(mChangeMode); 1153 assertWithMessage(mPropertyName + " must be " + mPropertyType + " type property") 1154 .that(carPropertyConfig.getPropertyType()) 1155 .isEqualTo(mPropertyType); 1156 1157 int[] areaIds = carPropertyConfig.getAreaIds(); 1158 assertWithMessage(mPropertyName + "'s must have at least 1 area ID defined") 1159 .that(areaIds.length).isAtLeast(1); 1160 assertWithMessage(mPropertyName + "'s area IDs must all be unique: " + Arrays.toString( 1161 areaIds)).that(ImmutableSet.copyOf(Arrays.stream( 1162 areaIds).boxed().collect(Collectors.toList())).size() 1163 == areaIds.length).isTrue(); 1164 1165 if (mAreaType == VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL) { 1166 assertWithMessage( 1167 mPropertyName 1168 + "'s AreaIds must contain a single 0 since it is " 1169 + areaTypeToString(mAreaType)) 1170 .that(areaIds) 1171 .isEqualTo(new int[] {0}); 1172 } else if (mAreaType == VehicleAreaType.VEHICLE_AREA_TYPE_WHEEL) { 1173 verifyValidAreaIdsForAreaType(ALL_POSSIBLE_WHEEL_AREA_IDS); 1174 verifyNoAreaOverlapInAreaIds(WHEEL_AREAS); 1175 } else if (mAreaType == VehicleAreaType.VEHICLE_AREA_TYPE_WINDOW) { 1176 verifyValidAreaIdsForAreaType(ALL_POSSIBLE_WINDOW_AREA_IDS); 1177 verifyNoAreaOverlapInAreaIds(WINDOW_AREAS); 1178 } else if (mAreaType == VehicleAreaType.VEHICLE_AREA_TYPE_MIRROR) { 1179 verifyValidAreaIdsForAreaType(ALL_POSSIBLE_MIRROR_AREA_IDS); 1180 verifyNoAreaOverlapInAreaIds(MIRROR_AREAS); 1181 } else if (mAreaType == VehicleAreaType.VEHICLE_AREA_TYPE_SEAT 1182 && mPropertyId != VehiclePropertyIds.INFO_DRIVER_SEAT) { 1183 verifyValidAreaIdsForAreaType(ALL_POSSIBLE_SEAT_AREA_IDS); 1184 verifyNoAreaOverlapInAreaIds(SEAT_AREAS); 1185 } else if (mAreaType == VehicleAreaType.VEHICLE_AREA_TYPE_DOOR) { 1186 verifyValidAreaIdsForAreaType(ALL_POSSIBLE_DOOR_AREA_IDS); 1187 verifyNoAreaOverlapInAreaIds(DOOR_AREAS); 1188 } 1189 if (mAreaIdsVerifier.isPresent()) { 1190 mAreaIdsVerifier.get().verify(areaIds); 1191 } 1192 1193 if (mChangeMode == CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS) { 1194 verifyContinuousCarPropertyConfig(); 1195 } else { 1196 verifyNonContinuousCarPropertyConfig(); 1197 } 1198 1199 mCarPropertyConfigVerifier.ifPresent( 1200 carPropertyConfigVerifier -> carPropertyConfigVerifier.verify(carPropertyConfig)); 1201 1202 if (!mPossibleConfigArrayValues.isEmpty()) { 1203 assertWithMessage(mPropertyName + " configArray must specify supported values") 1204 .that(carPropertyConfig.getConfigArray().size()) 1205 .isGreaterThan(0); 1206 for (Integer supportedValue : carPropertyConfig.getConfigArray()) { 1207 assertWithMessage( 1208 mPropertyName 1209 + " configArray value must be a defined " 1210 + "value: " 1211 + supportedValue) 1212 .that(supportedValue) 1213 .isIn(mPossibleConfigArrayValues); 1214 } 1215 } 1216 1217 mConfigArrayVerifier.ifPresent(configArrayVerifier -> configArrayVerifier.verify( 1218 carPropertyConfig.getConfigArray())); 1219 1220 if (mPossibleConfigArrayValues.isEmpty() && !mConfigArrayVerifier.isPresent() 1221 && !mCarPropertyConfigVerifier.isPresent()) { 1222 assertWithMessage(mPropertyName + " configArray is undefined, so it must be empty") 1223 .that(carPropertyConfig.getConfigArray().size()) 1224 .isEqualTo(0); 1225 } 1226 1227 for (int areaId : areaIds) { 1228 T areaIdMinValue = (T) carPropertyConfig.getMinValue(areaId); 1229 T areaIdMaxValue = (T) carPropertyConfig.getMaxValue(areaId); 1230 if (mRequireMinMaxValues) { 1231 assertWithMessage(mPropertyName + " - area ID: " + areaId 1232 + " must have min value defined").that(areaIdMinValue).isNotNull(); 1233 assertWithMessage(mPropertyName + " - area ID: " + areaId 1234 + " must have max value defined").that(areaIdMaxValue).isNotNull(); 1235 } 1236 if (mRequireMinValuesToBeZero) { 1237 assertWithMessage( 1238 mPropertyName + " - area ID: " + areaId + " min value must be zero").that( 1239 areaIdMinValue).isEqualTo(0); 1240 } 1241 if (mRequireZeroToBeContainedInMinMaxRanges) { 1242 assertWithMessage(mPropertyName + " - areaId: " + areaId 1243 + "'s max and min range must contain zero").that( 1244 verifyMaxAndMinRangeContainsZero(areaIdMinValue, areaIdMaxValue)).isTrue(); 1245 1246 } 1247 if (areaIdMinValue != null || areaIdMaxValue != null) { 1248 assertWithMessage( 1249 mPropertyName 1250 + " - areaId: " 1251 + areaId 1252 + "'s max value must be >= min value") 1253 .that(verifyMaxAndMin(areaIdMinValue, areaIdMaxValue)) 1254 .isTrue(); 1255 } 1256 1257 if (mRequirePropertyValueToBeInConfigArray) { 1258 List<?> supportedEnumValues = carPropertyConfig.getAreaIdConfig( 1259 areaId).getSupportedEnumValues(); 1260 assertWithMessage(mPropertyName + " - areaId: " + areaId 1261 + "'s supported enum values must match the values in the config array.") 1262 .that(carPropertyConfig.getConfigArray()) 1263 .containsExactlyElementsIn(supportedEnumValues); 1264 } 1265 1266 if (mChangeMode == CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE 1267 && !mAllPossibleEnumValues.isEmpty()) { 1268 List<?> supportedEnumValues = carPropertyConfig.getAreaIdConfig( 1269 areaId).getSupportedEnumValues(); 1270 assertWithMessage(mPropertyName + " - areaId: " + areaId 1271 + "'s supported enum values must be defined").that( 1272 supportedEnumValues).isNotEmpty(); 1273 assertWithMessage(mPropertyName + " - areaId: " + areaId 1274 + "'s supported enum values must not contain any duplicates").that( 1275 supportedEnumValues).containsNoDuplicates(); 1276 assertWithMessage( 1277 mPropertyName + " - areaId: " + areaId + "'s supported enum values " 1278 + supportedEnumValues + " must all exist in all possible enum set " 1279 + mAllPossibleEnumValues).that( 1280 mAllPossibleEnumValues.containsAll(supportedEnumValues)).isTrue(); 1281 } else { 1282 assertWithMessage(mPropertyName + " - areaId: " + areaId 1283 + "'s supported enum values must be empty since property does not support" 1284 + " an enum").that( 1285 carPropertyConfig.getAreaIdConfig( 1286 areaId).getSupportedEnumValues()).isEmpty(); 1287 } 1288 } 1289 } 1290 verifyMaxAndMinRangeContainsZero(T min, T max)1291 private boolean verifyMaxAndMinRangeContainsZero(T min, T max) { 1292 int propertyType = mPropertyId & VehiclePropertyType.MASK; 1293 switch (propertyType) { 1294 case VehiclePropertyType.INT32: 1295 return (Integer) max >= 0 && (Integer) min <= 0; 1296 case VehiclePropertyType.INT64: 1297 return (Long) max >= 0 && (Long) min <= 0; 1298 case VehiclePropertyType.FLOAT: 1299 return (Float) max >= 0 && (Float) min <= 0; 1300 default: 1301 return false; 1302 } 1303 } 1304 verifyMaxAndMin(T min, T max)1305 private boolean verifyMaxAndMin(T min, T max) { 1306 int propertyType = mPropertyId & VehiclePropertyType.MASK; 1307 switch (propertyType) { 1308 case VehiclePropertyType.INT32: 1309 return (Integer) max >= (Integer) min; 1310 case VehiclePropertyType.INT64: 1311 return (Long) max >= (Long) min; 1312 case VehiclePropertyType.FLOAT: 1313 return (Float) max >= (Float) min; 1314 default: 1315 return false; 1316 } 1317 } 1318 verifyContinuousCarPropertyConfig()1319 private void verifyContinuousCarPropertyConfig() { 1320 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig(); 1321 assertWithMessage( 1322 mPropertyName 1323 + " must define max sample rate since change mode is " 1324 + changeModeToString(mChangeMode)) 1325 .that(carPropertyConfig.getMaxSampleRate()) 1326 .isGreaterThan(0); 1327 assertWithMessage( 1328 mPropertyName 1329 + " must define min sample rate since change mode is " 1330 + changeModeToString(mChangeMode)) 1331 .that(carPropertyConfig.getMinSampleRate()) 1332 .isGreaterThan(0); 1333 assertWithMessage(mPropertyName + " max sample rate must be >= min sample rate") 1334 .that(carPropertyConfig.getMaxSampleRate() >= carPropertyConfig.getMinSampleRate()) 1335 .isTrue(); 1336 } 1337 verifyNonContinuousCarPropertyConfig()1338 private void verifyNonContinuousCarPropertyConfig() { 1339 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig(); 1340 assertWithMessage( 1341 mPropertyName 1342 + " must define max sample rate as 0 since change mode is " 1343 + changeModeToString(mChangeMode)) 1344 .that(carPropertyConfig.getMaxSampleRate()) 1345 .isEqualTo(0); 1346 assertWithMessage( 1347 mPropertyName 1348 + " must define min sample rate as 0 since change mode is " 1349 + changeModeToString(mChangeMode)) 1350 .that(carPropertyConfig.getMinSampleRate()) 1351 .isEqualTo(0); 1352 } 1353 verifyCarPropertyValueGetter()1354 private void verifyCarPropertyValueGetter() { 1355 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig(); 1356 if (carPropertyConfig.getAccess() == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE) { 1357 verifyGetPropertyFails(); 1358 return; 1359 } 1360 for (int areaId : carPropertyConfig.getAreaIds()) { 1361 CarPropertyValue<?> carPropertyValue = null; 1362 try { 1363 carPropertyValue = mCarPropertyManager.getProperty(mPropertyId, areaId); 1364 } catch (PropertyNotAvailableException e) { 1365 verifyPropertyNotAvailableException(e); 1366 // If the property is not available for getting, continue. 1367 continue; 1368 } catch (CarInternalErrorException e) { 1369 verifyInternalErrorException(e); 1370 continue; 1371 } 1372 1373 verifyCarPropertyValue(carPropertyValue, areaId, CAR_PROPERTY_VALUE_SOURCE_GETTER); 1374 if (mPropertyId == VehiclePropertyIds.HVAC_TEMPERATURE_VALUE_SUGGESTION) { 1375 verifyHvacTemperatureValueSuggestionResponse((Float[]) carPropertyValue.getValue()); 1376 } 1377 } 1378 } 1379 verifyGetPropertyFails()1380 private void verifyGetPropertyFails() { 1381 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig(); 1382 assertThrows( 1383 mPropertyName 1384 + " is a write_only property so getProperty should throw an" 1385 + " IllegalArgumentException.", 1386 IllegalArgumentException.class, 1387 () -> mCarPropertyManager.getProperty(mPropertyId, 1388 carPropertyConfig.getAreaIds()[0])); 1389 } 1390 verifyPropertyNotAvailableException(PropertyNotAvailableException e)1391 private static void verifyPropertyNotAvailableException(PropertyNotAvailableException e) { 1392 assertThat(((PropertyNotAvailableException) e).getDetailedErrorCode()) 1393 .isIn(PROPERTY_NOT_AVAILABLE_ERROR_CODES); 1394 int vendorErrorCode = e.getVendorErrorCode(); 1395 assertThat(vendorErrorCode).isAtLeast(VENDOR_ERROR_CODE_MINIMUM_VALUE); 1396 assertThat(vendorErrorCode).isAtMost(VENDOR_ERROR_CODE_MAXIMUM_VALUE); 1397 } 1398 verifyInternalErrorException(CarInternalErrorException e)1399 private static void verifyInternalErrorException(CarInternalErrorException e) { 1400 int vendorErrorCode = e.getVendorErrorCode(); 1401 assertThat(vendorErrorCode).isAtLeast(VENDOR_ERROR_CODE_MINIMUM_VALUE); 1402 assertThat(vendorErrorCode).isAtMost(VENDOR_ERROR_CODE_MAXIMUM_VALUE); 1403 } 1404 verifyCarPropertyValue(CarPropertyValue<?> carPropertyValue, int expectedAreaId, String source)1405 private void verifyCarPropertyValue(CarPropertyValue<?> carPropertyValue, int expectedAreaId, 1406 String source) { 1407 verifyCarPropertyValue(carPropertyValue.getPropertyId(), 1408 carPropertyValue.getAreaId(), carPropertyValue.getStatus(), 1409 carPropertyValue.getTimestamp(), (T) carPropertyValue.getValue(), expectedAreaId, 1410 source); 1411 } 1412 verifyCarPropertyValue( int propertyId, int areaId, int status, long timestampNanos, T value, int expectedAreaId, String source)1413 private void verifyCarPropertyValue( 1414 int propertyId, int areaId, int status, long timestampNanos, T value, 1415 int expectedAreaId, String source) { 1416 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig(); 1417 mCarPropertyValueVerifier.ifPresent( 1418 propertyValueVerifier -> propertyValueVerifier.verify(carPropertyConfig, propertyId, 1419 areaId, timestampNanos, value)); 1420 assertWithMessage( 1421 mPropertyName 1422 + " - areaId: " 1423 + areaId 1424 + " - source: " 1425 + source 1426 + " value must have correct property ID") 1427 .that(propertyId) 1428 .isEqualTo(mPropertyId); 1429 assertWithMessage( 1430 mPropertyName 1431 + " - areaId: " 1432 + areaId 1433 + " - source: " 1434 + source 1435 + " value must have correct area id: " 1436 + areaId) 1437 .that(areaId) 1438 .isEqualTo(expectedAreaId); 1439 assertWithMessage(mPropertyName + " - areaId: " + areaId + " - source: " + source 1440 + " area ID must be in carPropertyConfig#getAreaIds()").that(Arrays.stream( 1441 carPropertyConfig.getAreaIds()).boxed().collect(Collectors.toList()).contains( 1442 areaId)).isTrue(); 1443 assertWithMessage( 1444 mPropertyName 1445 + " - areaId: " 1446 + areaId 1447 + " - source: " 1448 + source 1449 + " value must have AVAILABLE status") 1450 .that(status) 1451 .isEqualTo(CarPropertyValue.STATUS_AVAILABLE); 1452 assertWithMessage( 1453 mPropertyName 1454 + " - areaId: " 1455 + areaId 1456 + " - source: " 1457 + source 1458 + " timestamp must use the SystemClock.elapsedRealtimeNanos() time" 1459 + " base") 1460 .that(timestampNanos) 1461 .isAtLeast(0); 1462 assertWithMessage( 1463 mPropertyName 1464 + " - areaId: " 1465 + areaId 1466 + " - source: " 1467 + source 1468 + " timestamp must use the SystemClock.elapsedRealtimeNanos() time" 1469 + " base") 1470 .that(timestampNanos) 1471 .isLessThan(SystemClock.elapsedRealtimeNanos()); 1472 assertWithMessage( 1473 mPropertyName 1474 + " - areaId: " 1475 + areaId 1476 + " - source: " 1477 + source 1478 + " must return " 1479 + mPropertyType 1480 + " type value") 1481 .that(value.getClass()) 1482 .isEqualTo(mPropertyType); 1483 1484 if (mRequirePropertyValueToBeInConfigArray) { 1485 assertWithMessage( 1486 mPropertyName 1487 + " - areaId: " 1488 + areaId 1489 + " - source: " 1490 + source 1491 + " value must be listed in configArray," 1492 + " configArray:") 1493 .that(carPropertyConfig.getConfigArray()) 1494 .contains(value); 1495 } 1496 1497 List<T> supportedEnumValues = carPropertyConfig.getAreaIdConfig( 1498 areaId).getSupportedEnumValues(); 1499 if (!supportedEnumValues.isEmpty()) { 1500 assertWithMessage(mPropertyName + " - areaId: " + areaId + " - source: " + source 1501 + " value must be listed in getSupportedEnumValues()").that(value).isIn( 1502 supportedEnumValues); 1503 } 1504 1505 T areaIdMinValue = (T) carPropertyConfig.getMinValue(areaId); 1506 T areaIdMaxValue = (T) carPropertyConfig.getMaxValue(areaId); 1507 if (areaIdMinValue != null && areaIdMaxValue != null) { 1508 assertWithMessage( 1509 "Property value: " + value + " must be between the max: " 1510 + areaIdMaxValue + " and min: " + areaIdMinValue 1511 + " values for area ID: " + Integer.toHexString(areaId)).that( 1512 verifyValueInRange( 1513 areaIdMinValue, 1514 areaIdMaxValue, 1515 (T) value)) 1516 .isTrue(); 1517 } 1518 1519 if (mVerifyErrorStates) { 1520 assertWithMessage( 1521 "When ADAS feature is enabled, " 1522 + VehiclePropertyIds.toString(mPropertyId) 1523 + " must not be set to " + ErrorState.NOT_AVAILABLE_DISABLED 1524 + " (ErrorState#NOT_AVAILABLE_DISABLED") 1525 .that((Integer) value) 1526 .isNotEqualTo(ErrorState.NOT_AVAILABLE_DISABLED); 1527 } 1528 } 1529 verifyValueInRange(T min, T max, T value)1530 private boolean verifyValueInRange(T min, T max, T value) { 1531 int propertyType = mPropertyId & VehiclePropertyType.MASK; 1532 switch (propertyType) { 1533 case VehiclePropertyType.INT32: 1534 return ((Integer) value >= (Integer) min && (Integer) value <= (Integer) max); 1535 case VehiclePropertyType.INT64: 1536 return ((Long) value >= (Long) min && (Long) value <= (Long) max); 1537 case VehiclePropertyType.FLOAT: 1538 return ((Float) value >= (Float) min && (Float) value <= (Float) max); 1539 default: 1540 return false; 1541 } 1542 } 1543 generateAllPossibleAreaIds(ImmutableSet<Integer> areas)1544 private static ImmutableSet<Integer> generateAllPossibleAreaIds(ImmutableSet<Integer> areas) { 1545 ImmutableSet.Builder<Integer> allPossibleAreaIdsBuilder = ImmutableSet.builder(); 1546 for (int i = 1; i <= areas.size(); i++) { 1547 allPossibleAreaIdsBuilder.addAll(Sets.combinations(areas, i).stream().map(areaCombo -> { 1548 Integer possibleAreaId = 0; 1549 for (Integer area : areaCombo) { 1550 possibleAreaId |= area; 1551 } 1552 return possibleAreaId; 1553 }).collect(Collectors.toList())); 1554 } 1555 return allPossibleAreaIdsBuilder.build(); 1556 } 1557 verifyValidAreaIdsForAreaType(ImmutableSet<Integer> allPossibleAreaIds)1558 private void verifyValidAreaIdsForAreaType(ImmutableSet<Integer> allPossibleAreaIds) { 1559 for (int areaId : getCarPropertyConfig().getAreaIds()) { 1560 assertWithMessage( 1561 mPropertyName + "'s area ID must be a valid " + areaTypeToString(mAreaType) 1562 + " area ID").that(areaId).isIn(allPossibleAreaIds); 1563 } 1564 } 1565 verifyNoAreaOverlapInAreaIds(ImmutableSet<Integer> areas)1566 private void verifyNoAreaOverlapInAreaIds(ImmutableSet<Integer> areas) { 1567 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig(); 1568 if (carPropertyConfig.getAreaIds().length < 2) { 1569 return; 1570 } 1571 ImmutableSet<Integer> areaIds = ImmutableSet.copyOf(Arrays.stream( 1572 carPropertyConfig.getAreaIds()).boxed().collect(Collectors.toList())); 1573 List<Integer> areaIdOverlapCheckResults = Sets.combinations(areaIds, 2).stream().map( 1574 areaIdPair -> { 1575 List<Integer> areaIdPairAsList = areaIdPair.stream().collect( 1576 Collectors.toList()); 1577 return areaIdPairAsList.get(0) & areaIdPairAsList.get(1); 1578 }).collect(Collectors.toList()); 1579 1580 assertWithMessage( 1581 mPropertyName + " area IDs: " + Arrays.toString(carPropertyConfig.getAreaIds()) 1582 + " must contain each area only once (e.g. no bitwise AND overlap) for " 1583 + "the area type: " + areaTypeToString(mAreaType)).that( 1584 Collections.frequency(areaIdOverlapCheckResults, 0) 1585 == areaIdOverlapCheckResults.size()).isTrue(); 1586 } 1587 verifyPermissionNotGrantedException()1588 private void verifyPermissionNotGrantedException() { 1589 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig(); 1590 assertWithMessage( 1591 mPropertyName 1592 + " - property ID: " 1593 + mPropertyId 1594 + " CarPropertyConfig should not be accessible without permissions.") 1595 .that(mCarPropertyManager.getCarPropertyConfig(mPropertyId)) 1596 .isNull(); 1597 1598 int access = carPropertyConfig.getAccess(); 1599 for (int areaId : carPropertyConfig.getAreaIds()) { 1600 if (access == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ 1601 || access == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE) { 1602 assertThrows( 1603 mPropertyName 1604 + " - property ID: " 1605 + mPropertyId 1606 + " - area ID: " 1607 + areaId 1608 + " should not be able to be read without permissions.", 1609 SecurityException.class, 1610 () -> mCarPropertyManager.getProperty(mPropertyId, areaId)); 1611 } 1612 if (access == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE 1613 || access == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE) { 1614 assertThrows( 1615 mPropertyName 1616 + " - property ID: " 1617 + mPropertyId 1618 + " - area ID: " 1619 + areaId 1620 + " should not be able to be written to without permissions.", 1621 SecurityException.class, 1622 () -> mCarPropertyManager.setProperty(mPropertyType, mPropertyId, areaId, 1623 getDefaultValue(mPropertyType))); 1624 } 1625 } 1626 1627 if (access == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE) { 1628 return; 1629 } 1630 1631 int updatesPerAreaId = getUpdatesPerAreaId(mChangeMode); 1632 long timeoutMillis = getRegisterCallbackTimeoutMillis(mChangeMode, 1633 carPropertyConfig.getMinSampleRate()); 1634 CarPropertyValueCallback carPropertyValueCallback = new CarPropertyValueCallback( 1635 mPropertyName, carPropertyConfig.getAreaIds(), updatesPerAreaId, timeoutMillis); 1636 1637 // We expect a return value of false and not a SecurityException thrown. 1638 // This is because registerCallback first tries to get the CarPropertyConfig for the 1639 // property, but since no permissions have been granted it can't find the CarPropertyConfig, 1640 // so it immediately returns false. 1641 assertWithMessage( 1642 mPropertyName 1643 + " - property ID: " 1644 + mPropertyId 1645 + " should not be able to be listened to without permissions.") 1646 .that( 1647 mCarPropertyManager.registerCallback(carPropertyValueCallback, mPropertyId, 1648 carPropertyConfig.getMaxSampleRate())) 1649 .isFalse(); 1650 } 1651 verifyHvacTemperatureValueSuggestionResponse(Float[] temperatureSuggestion)1652 private void verifyHvacTemperatureValueSuggestionResponse(Float[] temperatureSuggestion) { 1653 Float suggestedTempInCelsius = temperatureSuggestion[2]; 1654 Float suggestedTempInFahrenheit = temperatureSuggestion[3]; 1655 CarPropertyConfig<?> hvacTemperatureSetCarPropertyConfig = 1656 mCarPropertyManager.getCarPropertyConfig(VehiclePropertyIds.HVAC_TEMPERATURE_SET); 1657 if (hvacTemperatureSetCarPropertyConfig == null) { 1658 return; 1659 } 1660 List<Integer> hvacTemperatureSetConfigArray = 1661 hvacTemperatureSetCarPropertyConfig.getConfigArray(); 1662 1663 Integer minTempInCelsiusTimesTen = 1664 hvacTemperatureSetConfigArray.get(0); 1665 Integer maxTempInCelsiusTimesTen = 1666 hvacTemperatureSetConfigArray.get(1); 1667 Integer incrementInCelsiusTimesTen = 1668 hvacTemperatureSetConfigArray.get(2); 1669 verifyHvacTemperatureIsValid(suggestedTempInCelsius, minTempInCelsiusTimesTen, 1670 maxTempInCelsiusTimesTen, incrementInCelsiusTimesTen); 1671 1672 Integer minTempInFahrenheitTimesTen = 1673 hvacTemperatureSetConfigArray.get(3); 1674 Integer maxTempInFahrenheitTimesTen = 1675 hvacTemperatureSetConfigArray.get(4); 1676 Integer incrementInFahrenheitTimesTen = 1677 hvacTemperatureSetConfigArray.get(5); 1678 verifyHvacTemperatureIsValid(suggestedTempInFahrenheit, minTempInFahrenheitTimesTen, 1679 maxTempInFahrenheitTimesTen, incrementInFahrenheitTimesTen); 1680 1681 int suggestedTempInCelsiusTimesTen = suggestedTempInCelsius.intValue() * 10; 1682 int suggestedTempInFahrenheitTimesTen = suggestedTempInFahrenheit.intValue() * 10; 1683 int numIncrementsCelsius = 1684 (suggestedTempInCelsiusTimesTen - minTempInCelsiusTimesTen) 1685 / incrementInCelsiusTimesTen; 1686 int numIncrementsFahrenheit = 1687 (suggestedTempInFahrenheitTimesTen - minTempInFahrenheitTimesTen) 1688 / incrementInFahrenheitTimesTen; 1689 assertWithMessage( 1690 "The temperature in Celsius must be equivalent to the temperature in" 1691 + " Fahrenheit.") 1692 .that(numIncrementsFahrenheit) 1693 .isEqualTo(numIncrementsCelsius); 1694 } 1695 verifyHvacTemperatureIsValid(float temp, int minTempTimesTen, int maxTempTimesTen, int incrementTimesTen)1696 public static void verifyHvacTemperatureIsValid(float temp, int minTempTimesTen, 1697 int maxTempTimesTen, int incrementTimesTen) { 1698 Float tempMultiplied = temp * 10.0f; 1699 int intTempTimesTen = tempMultiplied.intValue(); 1700 assertWithMessage( 1701 "The temperature value " + intTempTimesTen + " must be at least " 1702 + minTempTimesTen + " and at most " + maxTempTimesTen) 1703 .that(intTempTimesTen >= minTempTimesTen && intTempTimesTen <= maxTempTimesTen) 1704 .isTrue(); 1705 1706 int remainder = (intTempTimesTen - minTempTimesTen) % incrementTimesTen; 1707 assertWithMessage( 1708 "The temperature value " + intTempTimesTen 1709 + " is not a valid temperature value. Valid values start from " 1710 + minTempTimesTen 1711 + " and increment by " + incrementTimesTen 1712 + " until the max temperature setting of " + maxTempTimesTen) 1713 .that(remainder) 1714 .isEqualTo(0); 1715 } 1716 1717 public interface ConfigArrayVerifier { verify(List<Integer> configArray)1718 void verify(List<Integer> configArray); 1719 } 1720 1721 public interface CarPropertyValueVerifier<T> { verify(CarPropertyConfig<T> carPropertyConfig, int propertyId, int areaId, long timestampNanos, T value)1722 void verify(CarPropertyConfig<T> carPropertyConfig, int propertyId, int areaId, 1723 long timestampNanos, T value); 1724 } 1725 1726 public interface AreaIdsVerifier { verify(int[] areaIds)1727 void verify(int[] areaIds); 1728 } 1729 1730 public interface CarPropertyConfigVerifier { verify(CarPropertyConfig<?> carPropertyConfig)1731 void verify(CarPropertyConfig<?> carPropertyConfig); 1732 } 1733 1734 public static class Builder<T> { 1735 private final int mPropertyId; 1736 private final int mAccess; 1737 private final int mAreaType; 1738 private final int mChangeMode; 1739 private final Class<T> mPropertyType; 1740 private final CarPropertyManager mCarPropertyManager; 1741 private boolean mRequiredProperty = false; 1742 private Optional<ConfigArrayVerifier> mConfigArrayVerifier = Optional.empty(); 1743 private Optional<CarPropertyValueVerifier<T>> mCarPropertyValueVerifier = Optional.empty(); 1744 private Optional<AreaIdsVerifier> mAreaIdsVerifier = Optional.empty(); 1745 private Optional<CarPropertyConfigVerifier> mCarPropertyConfigVerifier = Optional.empty(); 1746 private Optional<Integer> mDependentOnPropertyId = Optional.empty(); 1747 private ImmutableSet<String> mDependentOnPropertyPermissions = ImmutableSet.of(); 1748 private ImmutableSet<Integer> mPossibleConfigArrayValues = ImmutableSet.of(); 1749 private ImmutableSet<T> mAllPossibleEnumValues = ImmutableSet.of(); 1750 private ImmutableSet<T> mAllPossibleUnwritableValues = ImmutableSet.of(); 1751 private boolean mRequirePropertyValueToBeInConfigArray = false; 1752 private boolean mVerifySetterWithConfigArrayValues = false; 1753 private boolean mRequireMinMaxValues = false; 1754 private boolean mRequireMinValuesToBeZero = false; 1755 private boolean mRequireZeroToBeContainedInMinMaxRanges = false; 1756 private boolean mPossiblyDependentOnHvacPowerOn = false; 1757 private boolean mVerifyErrorStates = false; 1758 private final ImmutableSet.Builder<String> mReadPermissionsBuilder = ImmutableSet.builder(); 1759 private final ImmutableList.Builder<ImmutableSet<String>> mWritePermissionsBuilder = 1760 ImmutableList.builder(); 1761 Builder(int propertyId, int access, int areaType, int changeMode, Class<T> propertyType, CarPropertyManager carPropertyManager)1762 private Builder(int propertyId, int access, int areaType, int changeMode, 1763 Class<T> propertyType, CarPropertyManager carPropertyManager) { 1764 mPropertyId = propertyId; 1765 mAccess = access; 1766 mAreaType = areaType; 1767 mChangeMode = changeMode; 1768 mPropertyType = propertyType; 1769 mCarPropertyManager = carPropertyManager; 1770 } 1771 requireProperty()1772 public Builder<T> requireProperty() { 1773 mRequiredProperty = true; 1774 return this; 1775 } 1776 setConfigArrayVerifier(ConfigArrayVerifier configArrayVerifier)1777 public Builder<T> setConfigArrayVerifier(ConfigArrayVerifier configArrayVerifier) { 1778 mConfigArrayVerifier = Optional.of(configArrayVerifier); 1779 return this; 1780 } 1781 setCarPropertyValueVerifier( CarPropertyValueVerifier<T> carPropertyValueVerifier)1782 public Builder<T> setCarPropertyValueVerifier( 1783 CarPropertyValueVerifier<T> carPropertyValueVerifier) { 1784 mCarPropertyValueVerifier = Optional.of(carPropertyValueVerifier); 1785 return this; 1786 } 1787 setAreaIdsVerifier(AreaIdsVerifier areaIdsVerifier)1788 public Builder<T> setAreaIdsVerifier(AreaIdsVerifier areaIdsVerifier) { 1789 mAreaIdsVerifier = Optional.of(areaIdsVerifier); 1790 return this; 1791 } 1792 setCarPropertyConfigVerifier( CarPropertyConfigVerifier carPropertyConfigVerifier)1793 public Builder<T> setCarPropertyConfigVerifier( 1794 CarPropertyConfigVerifier carPropertyConfigVerifier) { 1795 mCarPropertyConfigVerifier = Optional.of(carPropertyConfigVerifier); 1796 return this; 1797 } 1798 setDependentOnProperty(Integer dependentPropertyId, ImmutableSet<String> dependentPropertyPermissions)1799 public Builder<T> setDependentOnProperty(Integer dependentPropertyId, 1800 ImmutableSet<String> dependentPropertyPermissions) { 1801 mDependentOnPropertyId = Optional.of(dependentPropertyId); 1802 mDependentOnPropertyPermissions = dependentPropertyPermissions; 1803 return this; 1804 } 1805 setPossibleConfigArrayValues( ImmutableSet<Integer> possibleConfigArrayValues)1806 public Builder<T> setPossibleConfigArrayValues( 1807 ImmutableSet<Integer> possibleConfigArrayValues) { 1808 mPossibleConfigArrayValues = possibleConfigArrayValues; 1809 return this; 1810 } 1811 setAllPossibleEnumValues(ImmutableSet<T> allPossibleEnumValues)1812 public Builder<T> setAllPossibleEnumValues(ImmutableSet<T> allPossibleEnumValues) { 1813 mAllPossibleEnumValues = allPossibleEnumValues; 1814 return this; 1815 } 1816 setAllPossibleUnwritableValues( ImmutableSet<T> allPossibleUnwritableValues)1817 public Builder<T> setAllPossibleUnwritableValues( 1818 ImmutableSet<T> allPossibleUnwritableValues) { 1819 mAllPossibleUnwritableValues = allPossibleUnwritableValues; 1820 return this; 1821 } 1822 requirePropertyValueTobeInConfigArray()1823 public Builder<T> requirePropertyValueTobeInConfigArray() { 1824 mRequirePropertyValueToBeInConfigArray = true; 1825 return this; 1826 } 1827 verifySetterWithConfigArrayValues()1828 public Builder<T> verifySetterWithConfigArrayValues() { 1829 mVerifySetterWithConfigArrayValues = true; 1830 return this; 1831 } 1832 requireMinMaxValues()1833 public Builder<T> requireMinMaxValues() { 1834 mRequireMinMaxValues = true; 1835 return this; 1836 } 1837 requireMinValuesToBeZero()1838 public Builder<T> requireMinValuesToBeZero() { 1839 mRequireMinValuesToBeZero = true; 1840 return this; 1841 } 1842 requireZeroToBeContainedInMinMaxRanges()1843 public Builder<T> requireZeroToBeContainedInMinMaxRanges() { 1844 mRequireZeroToBeContainedInMinMaxRanges = true; 1845 return this; 1846 } 1847 setPossiblyDependentOnHvacPowerOn()1848 public Builder<T> setPossiblyDependentOnHvacPowerOn() { 1849 mPossiblyDependentOnHvacPowerOn = true; 1850 return this; 1851 } 1852 verifyErrorStates()1853 public Builder<T> verifyErrorStates() { 1854 mVerifyErrorStates = true; 1855 return this; 1856 } 1857 addReadPermission(String readPermission)1858 public Builder<T> addReadPermission(String readPermission) { 1859 mReadPermissionsBuilder.add(readPermission); 1860 return this; 1861 } 1862 1863 /** 1864 * Add a single permission that alone can be used to update the property. Any set of 1865 * permissions in {@code mWritePermissionsBuilder} can be used to set the property. 1866 * 1867 * @param writePermission a permission used to update the property 1868 */ addWritePermission(String writePermission)1869 public Builder<T> addWritePermission(String writePermission) { 1870 mWritePermissionsBuilder.add(ImmutableSet.of(writePermission)); 1871 return this; 1872 } 1873 1874 /** 1875 * Add a set of permissions that together can be used to update the property. Any set of 1876 * permissions in {@code mWritePermissionsBuilder} can be used to set the property. 1877 * 1878 * @param writePermissionSet a set of permissions that together can be used to update the 1879 * property. 1880 */ addWritePermission(ImmutableSet<String> writePermissionSet)1881 public Builder<T> addWritePermission(ImmutableSet<String> writePermissionSet) { 1882 mWritePermissionsBuilder.add(writePermissionSet); 1883 return this; 1884 } 1885 build()1886 public VehiclePropertyVerifier<T> build() { 1887 return new VehiclePropertyVerifier<>( 1888 mCarPropertyManager, 1889 mPropertyId, 1890 mAccess, 1891 mAreaType, 1892 mChangeMode, 1893 mPropertyType, 1894 mRequiredProperty, 1895 mConfigArrayVerifier, 1896 mCarPropertyValueVerifier, 1897 mAreaIdsVerifier, 1898 mCarPropertyConfigVerifier, 1899 mDependentOnPropertyId, 1900 mDependentOnPropertyPermissions, 1901 mPossibleConfigArrayValues, 1902 mAllPossibleEnumValues, 1903 mAllPossibleUnwritableValues, 1904 mRequirePropertyValueToBeInConfigArray, 1905 mVerifySetterWithConfigArrayValues, 1906 mRequireMinMaxValues, 1907 mRequireMinValuesToBeZero, 1908 mRequireZeroToBeContainedInMinMaxRanges, 1909 mPossiblyDependentOnHvacPowerOn, 1910 mVerifyErrorStates, 1911 mReadPermissionsBuilder.build(), 1912 mWritePermissionsBuilder.build()); 1913 } 1914 } 1915 1916 private static class CarPropertyValueCallback implements 1917 CarPropertyManager.CarPropertyEventCallback { 1918 private final String mPropertyName; 1919 private final int[] mAreaIds; 1920 private final int mTotalCarPropertyValuesPerAreaId; 1921 private final CountDownLatch mCountDownLatch = new CountDownLatch(1); 1922 private final Object mLock = new Object(); 1923 @GuardedBy("mLock") 1924 private final SparseArray<List<CarPropertyValue<?>>> mAreaIdToCarPropertyValues = 1925 new SparseArray<>(); 1926 private final long mTimeoutMillis; 1927 CarPropertyValueCallback(String propertyName, int[] areaIds, int totalCarPropertyValuesPerAreaId, long timeoutMillis)1928 CarPropertyValueCallback(String propertyName, int[] areaIds, 1929 int totalCarPropertyValuesPerAreaId, long timeoutMillis) { 1930 mPropertyName = propertyName; 1931 mAreaIds = areaIds; 1932 mTotalCarPropertyValuesPerAreaId = totalCarPropertyValuesPerAreaId; 1933 mTimeoutMillis = timeoutMillis; 1934 synchronized (mLock) { 1935 for (int areaId : mAreaIds) { 1936 mAreaIdToCarPropertyValues.put(areaId, new ArrayList<>()); 1937 } 1938 } 1939 } 1940 getAreaIdToCarPropertyValues()1941 public SparseArray<List<CarPropertyValue<?>>> getAreaIdToCarPropertyValues() { 1942 boolean awaitSuccess = false; 1943 try { 1944 awaitSuccess = mCountDownLatch.await(mTimeoutMillis, TimeUnit.MILLISECONDS); 1945 } catch (InterruptedException e) { 1946 assertWithMessage("Waiting for onChangeEvent callback(s) for " + mPropertyName 1947 + " threw an exception: " + e).fail(); 1948 } 1949 synchronized (mLock) { 1950 assertWithMessage("Never received " + mTotalCarPropertyValuesPerAreaId 1951 + " CarPropertyValues for all " + mPropertyName + "'s areaIds: " 1952 + Arrays.toString(mAreaIds) + " before " + mTimeoutMillis + " ms timeout - " 1953 + mAreaIdToCarPropertyValues).that(awaitSuccess).isTrue(); 1954 return mAreaIdToCarPropertyValues.clone(); 1955 } 1956 } 1957 1958 @Override onChangeEvent(CarPropertyValue carPropertyValue)1959 public void onChangeEvent(CarPropertyValue carPropertyValue) { 1960 synchronized (mLock) { 1961 if (hasEnoughCarPropertyValuesForEachAreaIdLocked()) { 1962 return; 1963 } 1964 mAreaIdToCarPropertyValues.get(carPropertyValue.getAreaId()).add(carPropertyValue); 1965 if (hasEnoughCarPropertyValuesForEachAreaIdLocked()) { 1966 mCountDownLatch.countDown(); 1967 } 1968 } 1969 } 1970 1971 @GuardedBy("mLock") hasEnoughCarPropertyValuesForEachAreaIdLocked()1972 private boolean hasEnoughCarPropertyValuesForEachAreaIdLocked() { 1973 for (int areaId : mAreaIds) { 1974 List<CarPropertyValue<?>> carPropertyValues = mAreaIdToCarPropertyValues.get( 1975 areaId); 1976 if (carPropertyValues == null 1977 || carPropertyValues.size() < mTotalCarPropertyValuesPerAreaId) { 1978 return false; 1979 } 1980 } 1981 return true; 1982 } 1983 1984 @Override onErrorEvent(int propId, int zone)1985 public void onErrorEvent(int propId, int zone) { 1986 } 1987 1988 @Override onErrorEvent(int propId, int areaId, int errorCode)1989 public void onErrorEvent(int propId, int areaId, int errorCode) { 1990 } 1991 } 1992 1993 1994 private static class SetterCallback<T> implements CarPropertyManager.CarPropertyEventCallback { 1995 private final int mPropertyId; 1996 private final String mPropertyName; 1997 private final int mAreaId; 1998 private final T mExpectedSetValue; 1999 private final CountDownLatch mCountDownLatch = new CountDownLatch(1); 2000 private final long mCreationTimeNanos = SystemClock.elapsedRealtimeNanos(); 2001 private CarPropertyValue<?> mUpdatedCarPropertyValue = null; 2002 private T mReceivedValue = null; 2003 SetterCallback(int propertyId, int areaId, T expectedSetValue)2004 SetterCallback(int propertyId, int areaId, T expectedSetValue) { 2005 mPropertyId = propertyId; 2006 mPropertyName = VehiclePropertyIds.toString(propertyId); 2007 mAreaId = areaId; 2008 mExpectedSetValue = expectedSetValue; 2009 } 2010 valueToString(T value)2011 private String valueToString(T value) { 2012 if (value.getClass().isArray()) { 2013 return Arrays.toString((Object[]) value); 2014 } 2015 return value.toString(); 2016 } 2017 waitForUpdatedCarPropertyValue()2018 public CarPropertyValue<?> waitForUpdatedCarPropertyValue() { 2019 try { 2020 assertWithMessage( 2021 "Never received onChangeEvent(s) for " + mPropertyName + " new value: " 2022 + valueToString(mExpectedSetValue) + " before 5s timeout." 2023 + " Received: " 2024 + (mReceivedValue == null 2025 ? "No value" 2026 : valueToString(mReceivedValue))) 2027 .that(mCountDownLatch.await(5, TimeUnit.SECONDS)).isTrue(); 2028 } catch (InterruptedException e) { 2029 assertWithMessage("Waiting for onChangeEvent set callback for " 2030 + mPropertyName + " threw an exception: " + e).fail(); 2031 } 2032 return mUpdatedCarPropertyValue; 2033 } 2034 2035 @Override onChangeEvent(CarPropertyValue carPropertyValue)2036 public void onChangeEvent(CarPropertyValue carPropertyValue) { 2037 if (mUpdatedCarPropertyValue != null || carPropertyValue.getPropertyId() != mPropertyId 2038 || carPropertyValue.getAreaId() != mAreaId 2039 || carPropertyValue.getStatus() != CarPropertyValue.STATUS_AVAILABLE 2040 || carPropertyValue.getTimestamp() <= mCreationTimeNanos 2041 || carPropertyValue.getTimestamp() >= SystemClock.elapsedRealtimeNanos()) { 2042 return; 2043 } 2044 mReceivedValue = (T) carPropertyValue.getValue(); 2045 if (!valueEquals(mExpectedSetValue, mReceivedValue)) { 2046 return; 2047 } 2048 mUpdatedCarPropertyValue = carPropertyValue; 2049 mCountDownLatch.countDown(); 2050 } 2051 2052 @Override onErrorEvent(int propId, int zone)2053 public void onErrorEvent(int propId, int zone) { 2054 } 2055 } 2056 valueEquals(V v1, V v2)2057 private static <V> boolean valueEquals(V v1, V v2) { 2058 return (v1 instanceof Float && floatEquals((Float) v1, (Float) v2)) 2059 || (v1 instanceof Float[] && floatArrayEquals((Float[]) v1, (Float[]) v2)) 2060 || (v1 instanceof Long[] && longArrayEquals((Long[]) v1, (Long[]) v2)) 2061 || (v1 instanceof Integer[] && integerArrayEquals((Integer[]) v1, (Integer[]) v2)) 2062 || v1.equals(v2); 2063 } 2064 floatEquals(float f1, float f2)2065 private static boolean floatEquals(float f1, float f2) { 2066 return Math.abs(f1 - f2) < FLOAT_INEQUALITY_THRESHOLD; 2067 } 2068 floatArrayEquals(Float[] f1, Float[] f2)2069 private static boolean floatArrayEquals(Float[] f1, Float[] f2) { 2070 return Arrays.equals(f1, f2); 2071 } 2072 longArrayEquals(Long[] l1, Long[] l2)2073 private static boolean longArrayEquals(Long[] l1, Long[] l2) { 2074 return Arrays.equals(l1, l2); 2075 } 2076 integerArrayEquals(Integer[] i1, Integer[] i2)2077 private static boolean integerArrayEquals(Integer[] i1, Integer[] i2) { 2078 return Arrays.equals(i1, i2); 2079 } 2080 2081 private class TestGetPropertyCallback implements GetPropertyCallback { 2082 private final CountDownLatch mCountDownLatch; 2083 private final int mGetPropertyResultsCount; 2084 private final Object mLock = new Object(); 2085 @GuardedBy("mLock") 2086 private final List<GetPropertyResult<?>> mGetPropertyResults = new ArrayList<>(); 2087 @GuardedBy("mLock") 2088 private final List<PropertyAsyncError> mPropertyAsyncErrors = new ArrayList<>(); 2089 waitForResults()2090 public void waitForResults() { 2091 try { 2092 assertWithMessage("Received " + (mGetPropertyResultsCount 2093 - mCountDownLatch.getCount()) + " onSuccess(s), expected " 2094 + mGetPropertyResultsCount + " onSuccess(s)").that(mCountDownLatch.await( 2095 5, TimeUnit.SECONDS)).isTrue(); 2096 } catch (InterruptedException e) { 2097 assertWithMessage("Waiting for onSuccess threw an exception: " + e).fail(); 2098 } 2099 } getGetPropertyResults()2100 public List<GetPropertyResult<?>> getGetPropertyResults() { 2101 synchronized (mLock) { 2102 return mGetPropertyResults; 2103 } 2104 } 2105 getPropertyAsyncErrors()2106 public List<PropertyAsyncError> getPropertyAsyncErrors() { 2107 synchronized (mLock) { 2108 return mPropertyAsyncErrors; 2109 } 2110 } 2111 2112 @Override onSuccess(GetPropertyResult getPropertyResult)2113 public void onSuccess(GetPropertyResult getPropertyResult) { 2114 synchronized (mLock) { 2115 mGetPropertyResults.add(getPropertyResult); 2116 mCountDownLatch.countDown(); 2117 } 2118 } 2119 2120 @Override onFailure(PropertyAsyncError propertyAsyncError)2121 public void onFailure(PropertyAsyncError propertyAsyncError) { 2122 synchronized (mLock) { 2123 mPropertyAsyncErrors.add(propertyAsyncError); 2124 mCountDownLatch.countDown(); 2125 } 2126 } 2127 TestGetPropertyCallback(int getPropertyResultsCount)2128 TestGetPropertyCallback(int getPropertyResultsCount) { 2129 mCountDownLatch = new CountDownLatch(getPropertyResultsCount); 2130 mGetPropertyResultsCount = getPropertyResultsCount; 2131 } 2132 } 2133 2134 private class TestSetPropertyCallback implements SetPropertyCallback { 2135 private final CountDownLatch mCountDownLatch; 2136 private final int mSetPropertyResultsCount; 2137 private final Object mLock = new Object(); 2138 @GuardedBy("mLock") 2139 private final List<SetPropertyResult> mSetPropertyResults = new ArrayList<>(); 2140 @GuardedBy("mLock") 2141 private final List<PropertyAsyncError> mPropertyAsyncErrors = new ArrayList<>(); 2142 waitForResults()2143 public void waitForResults() { 2144 try { 2145 assertWithMessage("Received " + (mSetPropertyResultsCount 2146 - mCountDownLatch.getCount()) + " onSuccess(s), expected " 2147 + mSetPropertyResultsCount + " onSuccess(s)").that(mCountDownLatch.await( 2148 5, TimeUnit.SECONDS)).isTrue(); 2149 } catch (InterruptedException e) { 2150 assertWithMessage("Waiting for onSuccess threw an exception: " + e 2151 ).fail(); 2152 } 2153 } getSetPropertyResults()2154 public List<SetPropertyResult> getSetPropertyResults() { 2155 synchronized (mLock) { 2156 return mSetPropertyResults; 2157 } 2158 } 2159 getPropertyAsyncErrors()2160 public List<PropertyAsyncError> getPropertyAsyncErrors() { 2161 synchronized (mLock) { 2162 return mPropertyAsyncErrors; 2163 } 2164 } 2165 2166 @Override onSuccess(SetPropertyResult setPropertyResult)2167 public void onSuccess(SetPropertyResult setPropertyResult) { 2168 synchronized (mLock) { 2169 mSetPropertyResults.add(setPropertyResult); 2170 mCountDownLatch.countDown(); 2171 } 2172 } 2173 2174 @Override onFailure(PropertyAsyncError propertyAsyncError)2175 public void onFailure(PropertyAsyncError propertyAsyncError) { 2176 synchronized (mLock) { 2177 mPropertyAsyncErrors.add(propertyAsyncError); 2178 mCountDownLatch.countDown(); 2179 } 2180 } 2181 TestSetPropertyCallback(int setPropertyResultsCount)2182 TestSetPropertyCallback(int setPropertyResultsCount) { 2183 mCountDownLatch = new CountDownLatch(setPropertyResultsCount); 2184 mSetPropertyResultsCount = setPropertyResultsCount; 2185 } 2186 } 2187 verifyGetPropertiesAsync()2188 private void verifyGetPropertiesAsync() { 2189 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig(); 2190 if (carPropertyConfig.getAccess() == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE) { 2191 verifyGetPropertiesAsyncFails(); 2192 return; 2193 } 2194 2195 List<GetPropertyRequest> getPropertyRequests = new ArrayList<>(); 2196 SparseIntArray requestIdToAreaIdMap = new SparseIntArray(); 2197 for (int areaId : carPropertyConfig.getAreaIds()) { 2198 GetPropertyRequest getPropertyRequest = mCarPropertyManager.generateGetPropertyRequest( 2199 mPropertyId, areaId); 2200 int requestId = getPropertyRequest.getRequestId(); 2201 requestIdToAreaIdMap.put(requestId, areaId); 2202 getPropertyRequests.add(getPropertyRequest); 2203 } 2204 2205 TestGetPropertyCallback testGetPropertyCallback = new TestGetPropertyCallback( 2206 requestIdToAreaIdMap.size()); 2207 mCarPropertyManager.getPropertiesAsync(getPropertyRequests, /* cancellationSignal: */ null, 2208 /* callbackExecutor: */ null, testGetPropertyCallback); 2209 testGetPropertyCallback.waitForResults(); 2210 2211 for (GetPropertyResult<?> getPropertyResult : 2212 testGetPropertyCallback.getGetPropertyResults()) { 2213 int requestId = getPropertyResult.getRequestId(); 2214 int propertyId = getPropertyResult.getPropertyId(); 2215 if (requestIdToAreaIdMap.indexOfKey(requestId) < 0) { 2216 assertWithMessage( 2217 "getPropertiesAsync received GetPropertyResult with unknown requestId: " 2218 + getPropertyResult).fail(); 2219 } 2220 Integer expectedAreaId = requestIdToAreaIdMap.get(requestId); 2221 verifyCarPropertyValue(propertyId, getPropertyResult.getAreaId(), 2222 CarPropertyValue.STATUS_AVAILABLE, getPropertyResult.getTimestampNanos(), 2223 (T) getPropertyResult.getValue(), expectedAreaId, 2224 CAR_PROPERTY_VALUE_SOURCE_CALLBACK); 2225 if (mPropertyId == VehiclePropertyIds.HVAC_TEMPERATURE_VALUE_SUGGESTION) { 2226 verifyHvacTemperatureValueSuggestionResponse( 2227 (Float[]) getPropertyResult.getValue()); 2228 } 2229 } 2230 2231 for (PropertyAsyncError propertyAsyncError : 2232 testGetPropertyCallback.getPropertyAsyncErrors()) { 2233 int requestId = propertyAsyncError.getRequestId(); 2234 if (requestIdToAreaIdMap.indexOfKey(requestId) < 0) { 2235 assertWithMessage( 2236 "getPropertiesAsync received PropertyAsyncError with unknown requestId: " 2237 + propertyAsyncError).fail(); 2238 } 2239 assertWithMessage("Received PropertyAsyncError when testing getPropertiesAsync: " 2240 + propertyAsyncError).fail(); 2241 } 2242 } 2243 verifyGetPropertiesAsyncFails()2244 private void verifyGetPropertiesAsyncFails() { 2245 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig(); 2246 List<GetPropertyRequest> getPropertyRequests = new ArrayList<>(); 2247 GetPropertyRequest getPropertyRequest = mCarPropertyManager.generateGetPropertyRequest( 2248 mPropertyId, carPropertyConfig.getAreaIds()[0]); 2249 getPropertyRequests.add(getPropertyRequest); 2250 TestGetPropertyCallback testGetPropertyCallback = new TestGetPropertyCallback( 2251 /* getPropertyResultsCount: */ 1); 2252 assertThrows( 2253 mPropertyName 2254 + " is a write_only property so getPropertiesAsync should throw an" 2255 + " IllegalArgumentException.", 2256 IllegalArgumentException.class, 2257 () -> mCarPropertyManager.getPropertiesAsync(getPropertyRequests, 2258 /* cancellationSignal: */ null, /* callbackExecutor: */ null, 2259 testGetPropertyCallback)); 2260 } 2261 verifySetPropertiesAsync()2262 private void verifySetPropertiesAsync() { 2263 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig(); 2264 if (carPropertyConfig.getAccess() == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ) { 2265 verifySetPropertiesAsyncFails(); 2266 return; 2267 } 2268 2269 List<SetPropertyRequest<?>> setPropertyRequests = new ArrayList<>(); 2270 SparseIntArray requestIdToAreaIdMap = new SparseIntArray(); 2271 for (int areaId : carPropertyConfig.getAreaIds()) { 2272 Collection<T> possibleValues = getPossibleValues(areaId); 2273 if (possibleValues == null || possibleValues.size() == 0) { 2274 continue; 2275 } 2276 for (T possibleValue : possibleValues) { 2277 SetPropertyRequest setPropertyRequest = 2278 mCarPropertyManager.generateSetPropertyRequest(mPropertyId, areaId, 2279 possibleValue); 2280 if (carPropertyConfig.getAccess() 2281 == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE) { 2282 setPropertyRequest.setWaitForPropertyUpdate(false); 2283 } 2284 requestIdToAreaIdMap.put(setPropertyRequest.getRequestId(), areaId); 2285 setPropertyRequests.add(setPropertyRequest); 2286 } 2287 } 2288 2289 TestSetPropertyCallback testSetPropertyCallback = new TestSetPropertyCallback( 2290 requestIdToAreaIdMap.size()); 2291 mCarPropertyManager.setPropertiesAsync(setPropertyRequests, /* cancellationSignal: */ null, 2292 /* callbackExecutor: */ null, testSetPropertyCallback); 2293 testSetPropertyCallback.waitForResults(); 2294 2295 for (SetPropertyResult setPropertyResult : 2296 testSetPropertyCallback.getSetPropertyResults()) { 2297 int requestId = setPropertyResult.getRequestId(); 2298 if (requestIdToAreaIdMap.indexOfKey(requestId) < 0) { 2299 assertWithMessage( 2300 "setPropertiesAsync received SetPropertyResult with unknown requestId: " 2301 + setPropertyResult).fail(); 2302 } 2303 assertThat(setPropertyResult.getPropertyId()).isEqualTo(mPropertyId); 2304 assertThat(setPropertyResult.getAreaId()).isEqualTo( 2305 requestIdToAreaIdMap.get(requestId)); 2306 assertThat(setPropertyResult.getUpdateTimestampNanos()).isAtLeast(0); 2307 assertThat(setPropertyResult.getUpdateTimestampNanos()).isLessThan( 2308 SystemClock.elapsedRealtimeNanos()); 2309 } 2310 2311 for (PropertyAsyncError propertyAsyncError : 2312 testSetPropertyCallback.getPropertyAsyncErrors()) { 2313 int requestId = propertyAsyncError.getRequestId(); 2314 if (requestIdToAreaIdMap.indexOfKey(requestId) < 0) { 2315 assertWithMessage("setPropertiesAsync received PropertyAsyncError with unknown " 2316 + "requestId: " + propertyAsyncError).fail(); 2317 } 2318 assertWithMessage("Received PropertyAsyncError when testing setPropertiesAsync: " 2319 + propertyAsyncError).fail(); 2320 } 2321 } 2322 verifySetPropertiesAsyncFails()2323 private void verifySetPropertiesAsyncFails() { 2324 CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig(); 2325 List<SetPropertyRequest<?>> setPropertyRequests = new ArrayList<>(); 2326 SetPropertyRequest setPropertyRequest = mCarPropertyManager.generateSetPropertyRequest( 2327 mPropertyId, 2328 carPropertyConfig.getAreaIds()[0], 2329 getDefaultValue(carPropertyConfig.getPropertyType())); 2330 setPropertyRequests.add(setPropertyRequest); 2331 TestSetPropertyCallback testSetPropertyCallback = new TestSetPropertyCallback( 2332 /* setPropertyResultsCount: */ 1); 2333 assertThrows( 2334 mPropertyName 2335 + " is a read_only property so setPropertiesAsync should throw an" 2336 + " IllegalArgumentException.", 2337 IllegalArgumentException.class, 2338 () -> mCarPropertyManager.setPropertiesAsync(setPropertyRequests, 2339 /* cancellationSignal: */ null, /* callbackExecutor: */ null, 2340 testSetPropertyCallback)); 2341 } 2342 setPropertyAndWaitForChange( CarPropertyManager carPropertyManager, int propertyId, Class<U> propertyType, int areaId, U valueToSet)2343 private static <U> CarPropertyValue<U> setPropertyAndWaitForChange( 2344 CarPropertyManager carPropertyManager, int propertyId, Class<U> propertyType, 2345 int areaId, U valueToSet) { 2346 return setPropertyAndWaitForChange(carPropertyManager, propertyId, propertyType, areaId, 2347 valueToSet, valueToSet); 2348 } 2349 setPropertyAndWaitForChange( CarPropertyManager carPropertyManager, int propertyId, Class<U> propertyType, int areaId, U valueToSet, U expectedValueToGet)2350 private static <U> CarPropertyValue<U> setPropertyAndWaitForChange( 2351 CarPropertyManager carPropertyManager, int propertyId, Class<U> propertyType, 2352 int areaId, U valueToSet, U expectedValueToGet) { 2353 SetterCallback setterCallback = new SetterCallback(propertyId, areaId, expectedValueToGet); 2354 assertWithMessage("Failed to register setter callback for " + VehiclePropertyIds.toString( 2355 propertyId)).that(carPropertyManager.registerCallback(setterCallback, propertyId, 2356 CarPropertyManager.SENSOR_RATE_FASTEST)).isTrue(); 2357 try { 2358 carPropertyManager.setProperty(propertyType, propertyId, areaId, valueToSet); 2359 } catch (PropertyNotAvailableException e) { 2360 verifyPropertyNotAvailableException(e); 2361 return null; 2362 } catch (CarInternalErrorException e) { 2363 verifyInternalErrorException(e); 2364 return null; 2365 } 2366 2367 CarPropertyValue<U> carPropertyValue = setterCallback.waitForUpdatedCarPropertyValue(); 2368 carPropertyManager.unregisterCallback(setterCallback, propertyId); 2369 return carPropertyValue; 2370 } 2371 } 2372