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