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