• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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