• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.car.cts.utils;
18 
19 import static android.car.cts.utils.ShellPermissionUtils.CHECK_MODE_ASSUME;
20 import static android.car.cts.utils.ShellPermissionUtils.runWithShellPermissionIdentity;
21 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
22 
23 import static com.google.common.truth.Truth.assertThat;
24 import static com.google.common.truth.Truth.assertWithMessage;
25 
26 import static junit.framework.Assert.fail;
27 
28 import static org.junit.Assert.assertThrows;
29 import static org.junit.Assume.assumeThat;
30 import static org.junit.Assume.assumeTrue;
31 
32 import android.car.VehicleAreaDoor;
33 import android.car.VehicleAreaMirror;
34 import android.car.VehicleAreaSeat;
35 import android.car.VehicleAreaType;
36 import android.car.VehicleAreaWheel;
37 import android.car.VehicleAreaWindow;
38 import android.car.VehiclePropertyIds;
39 import android.car.VehiclePropertyType;
40 import android.car.feature.Flags;
41 import android.car.hardware.CarHvacFanDirection;
42 import android.car.hardware.CarPropertyConfig;
43 import android.car.hardware.CarPropertyValue;
44 import android.car.hardware.property.AreaIdConfig;
45 import android.car.hardware.property.CarInternalErrorException;
46 import android.car.hardware.property.CarPropertyManager;
47 import android.car.hardware.property.CarPropertyManager.GetPropertyCallback;
48 import android.car.hardware.property.CarPropertyManager.GetPropertyRequest;
49 import android.car.hardware.property.CarPropertyManager.GetPropertyResult;
50 import android.car.hardware.property.CarPropertyManager.PropertyAsyncError;
51 import android.car.hardware.property.CarPropertyManager.SetPropertyCallback;
52 import android.car.hardware.property.CarPropertyManager.SetPropertyRequest;
53 import android.car.hardware.property.CarPropertyManager.SetPropertyResult;
54 import android.car.hardware.property.CarPropertyManager.SupportedValuesChangeCallback;
55 import android.car.hardware.property.ErrorState;
56 import android.car.hardware.property.MinMaxSupportedValue;
57 import android.car.hardware.property.PropertyNotAvailableAndRetryException;
58 import android.car.hardware.property.PropertyNotAvailableErrorCode;
59 import android.car.hardware.property.PropertyNotAvailableException;
60 import android.car.hardware.property.Subscription;
61 import android.content.Context;
62 import android.os.Build;
63 import android.os.SystemClock;
64 import android.util.ArrayMap;
65 import android.util.Log;
66 import android.util.SparseArray;
67 import android.util.SparseIntArray;
68 
69 import androidx.annotation.Nullable;
70 import androidx.test.platform.app.InstrumentationRegistry;
71 
72 import com.android.bedstead.nene.TestApis;
73 import com.android.bedstead.permissions.PermissionContext;
74 import com.android.internal.annotations.GuardedBy;
75 
76 import com.google.common.collect.ImmutableList;
77 import com.google.common.collect.ImmutableSet;
78 import com.google.common.collect.Sets;
79 
80 import org.hamcrest.Matchers;
81 import org.junit.AssumptionViolatedException;
82 
83 import java.time.Duration;
84 import java.util.ArrayList;
85 import java.util.Arrays;
86 import java.util.Collection;
87 import java.util.Collections;
88 import java.util.List;
89 import java.util.Optional;
90 import java.util.Set;
91 import java.util.concurrent.CountDownLatch;
92 import java.util.concurrent.Executor;
93 import java.util.concurrent.TimeUnit;
94 import java.util.stream.Collectors;
95 import java.util.stream.IntStream;
96 
97 /**
98  * A class for verifying the implementation for one VHAL property.
99  *
100  * @param <T> The type for the property.
101  */
102 public class VehiclePropertyVerifier<T> {
103     private static final String STEP_VERIFY_READ_APIS_PREFIX = "verifyReadApis";
104     private static final String STEP_VERIFY_WRITE_APIS_PREFIX = "verifyWriteApis";
105 
106     private static final CarSvcPropsParser CAR_SVC_PROPS_PARSER = new CarSvcPropsParser();
107 
108     /** A step to verify {@link CarPropertyManager#getCarPropertyConfig} returns expected config. */
109     public static final String STEP_VERIFY_PROPERTY_CONFIG = "verifyPropertyConfig";
110 
111     /** A step to verify {@link CarPropertyManager#isPropertyAvailable}. */
112     public static final String STEP_VERIFY_IS_PROPERTY_AVAILABLE = "isPropertyAvailable";
113 
114     /** A step to verify that expected exceptions are thrown when missing permission. */
115     public static final String STEP_VERIFY_PERMISSION_NOT_GRANTED_EXCEPTION =
116             "verifyPermissionNotGrantedException";
117 
118     /**
119      * A step to verify {@link CarPropertyManager#getProperty}, {@link
120      * CarPropertyManager#getIntProperty}, {@link CarPropertyManager#getBooleanProperty}, {@link
121      * CarPropertyManager#getFloatProperty}, {@link CarPropertyManager#getIntArrayProperty}.
122      */
123     public static final String STEP_VERIFY_READ_APIS_GET_PROPERTY_SYNC =
124             STEP_VERIFY_READ_APIS_PREFIX + ".getProperty";
125 
126     /** A step to verify {@link CarPropertyManager#getPropertiesAsync}. */
127     public static final String STEP_VERIFY_READ_APIS_GET_PROPERTY_ASYNC =
128             STEP_VERIFY_READ_APIS_PREFIX + ".getPropertiesAsync";
129 
130     /** A step to verify {@link CarPropertyManager#subscribePropertyEvents}. */
131     public static final String STEP_VERIFY_READ_APIS_SUBSCRIBE =
132             STEP_VERIFY_READ_APIS_PREFIX + ".subscribePropertyEvents";
133 
134     /** A step to verify {@link CarPropertyManager#getMinMaxSupportedValue}. */
135     public static final String STEP_VERIFY_READ_APIS_GET_MIN_MAX_SUPPORTED_VALUE =
136             STEP_VERIFY_READ_APIS_PREFIX + ".getMinMaxSupportedValue";
137 
138     /** A step to verify {@link CarPropertyManager#getSupportedValuesList}. */
139     public static final String STEP_VERIFY_READ_APIS_GET_SUPPORTED_VALUES_LIST =
140             STEP_VERIFY_READ_APIS_PREFIX + ".getSupportedValuesList";
141     /**
142      * A step to verify {@link CarPropertyManager#registerSupportedValuesChangeCallback} and {@link
143      * CarPropertyManager#unregisterSupportedValuesChangeCallback}
144      */
145     public static final String STEP_VERIFY_READ_APIS_REG_UNREG_SUPPORTED_VALUES_CHANGE =
146             STEP_VERIFY_READ_APIS_PREFIX + ".regUnregSupportedValuesChangeCallback";
147 
148     /**
149      * A step to verify that for ADAS properties, if the feature is disabled, the property must
150      * report error state.
151      *
152      * <p>This step is skipped for non-adas properties.
153      */
154     public static final String STEP_VERIFY_READ_APIS_DISABLE_ADAS_FEATURE_VERIFY_STATE =
155             STEP_VERIFY_READ_APIS_PREFIX + ".disableAdasFeatureVerifyState";
156 
157     /** A step to verify {@link CarPropertyManager#setProperty}. */
158     public static final String STEP_VERIFY_WRITE_APIS_SET_PROPERTY_SYNC =
159             STEP_VERIFY_WRITE_APIS_PREFIX + ".setProperty";
160 
161     /** A step to verify {@link CarPropertyManager#setPropertiesAsync}. */
162     public static final String STEP_VERIFY_WRITE_APIS_SET_PROPERTY_ASYNC =
163             STEP_VERIFY_WRITE_APIS_PREFIX + ".setPropertiesAsync";
164 
165     /**
166      * A step to verify that for ADAS properties, if the feature is disabled, the property must
167      * report error state.
168      *
169      * <p>This step is skipped for non-adas properties.
170      */
171     public static final String STEP_VERIFY_WRITE_APIS_DISABLE_ADAS_FEATURE_VERIFY_STATE =
172             STEP_VERIFY_WRITE_APIS_PREFIX + ".disableAdasFeatureVerifyState";
173 
174     /**
175      * A step to verify that for HVAC power dependant properties, getting should be unavailable when
176      * HVAC power is off.
177      */
178     public static final String STEP_VERIFY_READ_APIS_DISABLE_HVAC_GET_NOT_AVAILABLE =
179             STEP_VERIFY_READ_APIS_PREFIX + ".turnOffHvacPowerGetNotAvailable";
180 
181     /**
182      * A step to verify that for HVAC power dependant properties, setting should be unavailable when
183      * HVAC power is off.
184      */
185     public static final String STEP_VERIFY_WRITE_APIS_DISABLE_HVAC_SET_NOT_AVAILABLE =
186             STEP_VERIFY_WRITE_APIS_PREFIX + ".turnOffHvacPowerSetNotAvailable";
187 
188     /**
189      * A step to verify that for read/write properties, if the caller only has read permission,
190      * caller cannot write.
191      */
192     public static final String STEP_VERIFY_READ_PERMISSION_CANNOT_WRITE =
193             "verifyReadPermissionCannotWrite";
194 
195     /**
196      * A step to verify that for read/write properties, if the caller only has write permission,
197      * caller cannot read.
198      */
199     public static final String STEP_VERIFY_WRITE_PERMISSION_CANNOT_READ =
200             "verifyWritePermissionCannotRead";
201 
202     private static final String TAG = VehiclePropertyVerifier.class.getSimpleName();
203     private static final String CAR_PROPERTY_VALUE_SOURCE_GETTER = "Getter";
204     private static final String CAR_PROPERTY_VALUE_SOURCE_CALLBACK = "Callback";
205     private static final int GLOBAL_AREA_ID = 0;
206     private static final float FLOAT_INEQUALITY_THRESHOLD = 0.00001f;
207     private static final int VENDOR_ERROR_CODE_MINIMUM_VALUE = 0x0;
208     private static final int VENDOR_ERROR_CODE_MAXIMUM_VALUE = 0xffff;
209     private static final int SET_PROPERTY_CALLBACK_TIMEOUT_SEC = 5;
210     private static final long CPM_ACTION_DELAY_MS = 20;
211     private static final Object sLock = new Object();
212     private static final ImmutableSet<Integer> WHEEL_AREAS =
213             ImmutableSet.of(
214                     VehicleAreaWheel.WHEEL_LEFT_FRONT, VehicleAreaWheel.WHEEL_LEFT_REAR,
215                     VehicleAreaWheel.WHEEL_RIGHT_FRONT, VehicleAreaWheel.WHEEL_RIGHT_REAR);
216     private static final ImmutableSet<Integer> ALL_POSSIBLE_WHEEL_AREA_IDS =
217             generateAllPossibleAreaIds(WHEEL_AREAS);
218     private static final ImmutableSet<Integer> WINDOW_AREAS =
219             ImmutableSet.of(
220                     VehicleAreaWindow.WINDOW_FRONT_WINDSHIELD,
221                             VehicleAreaWindow.WINDOW_REAR_WINDSHIELD,
222                     VehicleAreaWindow.WINDOW_ROW_1_LEFT, VehicleAreaWindow.WINDOW_ROW_1_RIGHT,
223                     VehicleAreaWindow.WINDOW_ROW_2_LEFT, VehicleAreaWindow.WINDOW_ROW_2_RIGHT,
224                     VehicleAreaWindow.WINDOW_ROW_3_LEFT, VehicleAreaWindow.WINDOW_ROW_3_RIGHT,
225                     VehicleAreaWindow.WINDOW_ROOF_TOP_1, VehicleAreaWindow.WINDOW_ROOF_TOP_2);
226     private static final ImmutableSet<Integer> ALL_POSSIBLE_WINDOW_AREA_IDS =
227             generateAllPossibleAreaIds(WINDOW_AREAS);
228     private static final ImmutableSet<Integer> MIRROR_AREAS =
229             ImmutableSet.of(
230                     VehicleAreaMirror.MIRROR_DRIVER_LEFT,
231                     VehicleAreaMirror.MIRROR_DRIVER_RIGHT,
232                     VehicleAreaMirror.MIRROR_DRIVER_CENTER);
233     private static final ImmutableSet<Integer> ALL_POSSIBLE_MIRROR_AREA_IDS =
234             generateAllPossibleAreaIds(MIRROR_AREAS);
235     private static final ImmutableSet<Integer> SEAT_AREAS =
236             ImmutableSet.of(
237                     VehicleAreaSeat.SEAT_ROW_1_LEFT,
238                     VehicleAreaSeat.SEAT_ROW_1_CENTER,
239                     VehicleAreaSeat.SEAT_ROW_1_RIGHT,
240                     VehicleAreaSeat.SEAT_ROW_2_LEFT,
241                     VehicleAreaSeat.SEAT_ROW_2_CENTER,
242                     VehicleAreaSeat.SEAT_ROW_2_RIGHT,
243                     VehicleAreaSeat.SEAT_ROW_3_LEFT,
244                     VehicleAreaSeat.SEAT_ROW_3_CENTER,
245                     VehicleAreaSeat.SEAT_ROW_3_RIGHT);
246     private static final ImmutableSet<Integer> ALL_POSSIBLE_SEAT_AREA_IDS =
247             generateAllPossibleAreaIds(SEAT_AREAS);
248     private static final ImmutableSet<Integer> DOOR_AREAS =
249             ImmutableSet.of(
250                     VehicleAreaDoor.DOOR_ROW_1_LEFT, VehicleAreaDoor.DOOR_ROW_1_RIGHT,
251                     VehicleAreaDoor.DOOR_ROW_2_LEFT, VehicleAreaDoor.DOOR_ROW_2_RIGHT,
252                     VehicleAreaDoor.DOOR_ROW_3_LEFT, VehicleAreaDoor.DOOR_ROW_3_RIGHT,
253                     VehicleAreaDoor.DOOR_HOOD, VehicleAreaDoor.DOOR_REAR);
254     private static final ImmutableSet<Integer> ALL_POSSIBLE_DOOR_AREA_IDS =
255             generateAllPossibleAreaIds(DOOR_AREAS);
256     private static final ImmutableSet<Integer> PROPERTY_NOT_AVAILABLE_ERROR_CODES =
257             ImmutableSet.of(
258                     PropertyNotAvailableErrorCode.NOT_AVAILABLE,
259                     PropertyNotAvailableErrorCode.NOT_AVAILABLE_DISABLED,
260                     PropertyNotAvailableErrorCode.NOT_AVAILABLE_SPEED_LOW,
261                     PropertyNotAvailableErrorCode.NOT_AVAILABLE_SPEED_HIGH,
262                     PropertyNotAvailableErrorCode.NOT_AVAILABLE_POOR_VISIBILITY,
263                     PropertyNotAvailableErrorCode.NOT_AVAILABLE_SAFETY);
264     private static final boolean AREA_ID_CONFIG_ACCESS_FLAG =
265             isAtLeastV() && Flags.areaIdConfigAccess();
266     private static final boolean CAR_PROPERTY_SUPPORTED_VALUE_FLAG =
267             isAtLeastB() && Flags.carPropertySupportedValue();
268     private static final List<Integer> VALID_CAR_PROPERTY_VALUE_STATUSES =
269             Arrays.asList(
270                     CarPropertyValue.STATUS_AVAILABLE,
271                     CarPropertyValue.STATUS_UNAVAILABLE,
272                     CarPropertyValue.STATUS_ERROR);
273     private static final List<Integer> VALID_SET_ERROR_CODES =
274             Arrays.asList(
275                     CarPropertyManager.CAR_SET_PROPERTY_ERROR_CODE_TRY_AGAIN,
276                     CarPropertyManager.CAR_SET_PROPERTY_ERROR_CODE_INVALID_ARG,
277                     CarPropertyManager.CAR_SET_PROPERTY_ERROR_CODE_PROPERTY_NOT_AVAILABLE,
278                     CarPropertyManager.CAR_SET_PROPERTY_ERROR_CODE_ACCESS_DENIED,
279                     CarPropertyManager.CAR_SET_PROPERTY_ERROR_CODE_UNKNOWN);
280 
281     private static final CarPropertyValueCallback FAKE_CALLBACK =
282             new CarPropertyValueCallback(
283                     /* propertyName= */ "",
284                     new int[] {},
285                     /* totalCarPropertyValuesPerAreaId= */ 0,
286                     /* timeoutMillis= */ 0);
287 
288     private static Class<?> sExceptionClassOnGet;
289     private static Class<?> sExceptionClassOnSet;
290 
291     @GuardedBy("sLock")
292     private static long sLastActionElapsedRealtimeNanos = 0;
293 
294     private static boolean sIsCarPropertyConfigsCached;
295     // A static cache to store all property configs. This will be reused across multiple test cases
296     // in order to save time.
297     private static SparseArray<CarPropertyConfig<?>> sCachedCarPropertyConfigs =
298             new SparseArray<>();
299 
300     private final Context mContext =
301             InstrumentationRegistry.getInstrumentation().getTargetContext();
302     private final CarPropertyManager mCarPropertyManager;
303     private final int mPropertyId;
304     private final String mPropertyName;
305     private final ImmutableSet<Integer> mAllowedAccessModes;
306     private final int mAreaType;
307     private final int mChangeMode;
308     private final Class<T> mPropertyType;
309     private final boolean mRequiredProperty;
310     private final Optional<ConfigArrayVerifier> mConfigArrayVerifier;
311     private final Optional<CarPropertyValueVerifier<T>> mCarPropertyValueVerifier;
312     private final Optional<AreaIdsVerifier> mAreaIdsVerifier;
313     private final Optional<CarPropertyConfigVerifier> mCarPropertyConfigVerifier;
314     private final Optional<Integer> mDependentOnPropertyId;
315     private final ImmutableSet<String> mDependentOnPropertyPermissions;
316     private final ImmutableSet<Integer> mPossibleConfigArrayValues;
317     private final boolean mEnumIsBitMap;
318     private final ImmutableSet<T> mAllPossibleEnumValues;
319     private final ImmutableSet<T> mAllPossibleUnwritableValues;
320     private final ImmutableSet<T> mAllPossibleUnavailableValues;
321     private final boolean mRequirePropertyValueToBeInConfigArray;
322     private final boolean mVerifySetterWithConfigArrayValues;
323     private final boolean mRequireMinMaxValues;
324     private final boolean mRequireMinValuesToBeZero;
325     private final boolean mRequireZeroToBeContainedInMinMaxRanges;
326     private final boolean mPossiblyDependentOnHvacPowerOn;
327     private final boolean mVerifyErrorStates;
328     // Delay to wait for power state to propagate to dependent properties.
329     private final int mPowerPropagationDelayMs;
330     private final ImmutableSet<String> mReadPermissions;
331     private final ImmutableList<ImmutableSet<String>> mWritePermissions;
332     private final VerifierContext mVerifierContext;
333     private final List<Integer> mStoredProperties = new ArrayList<>();
334 
335     private SparseArray<SparseArray<?>> mPropertyToAreaIdValues;
336 
VehiclePropertyVerifier( CarPropertyManager carPropertyManager, int propertyId, ImmutableSet<Integer> allowedAccessModes, 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, int powerPropagationDelayMs, ImmutableSet<String> readPermissions, ImmutableList<ImmutableSet<String>> writePermissions)337     private VehiclePropertyVerifier(
338             CarPropertyManager carPropertyManager,
339             int propertyId,
340             ImmutableSet<Integer> allowedAccessModes,
341             int areaType,
342             int changeMode,
343             Class<T> propertyType,
344             boolean requiredProperty,
345             Optional<ConfigArrayVerifier> configArrayVerifier,
346             Optional<CarPropertyValueVerifier<T>> carPropertyValueVerifier,
347             Optional<AreaIdsVerifier> areaIdsVerifier,
348             Optional<CarPropertyConfigVerifier> carPropertyConfigVerifier,
349             Optional<Integer> dependentPropertyId,
350             ImmutableSet<String> dependentOnPropertyPermissions,
351             ImmutableSet<Integer> possibleConfigArrayValues,
352             boolean enumIsBitMap,
353             ImmutableSet<T> allPossibleEnumValues,
354             ImmutableSet<T> allPossibleUnwritableValues,
355             ImmutableSet<T> allPossibleUnavailableValues,
356             boolean requirePropertyValueToBeInConfigArray,
357             boolean verifySetterWithConfigArrayValues,
358             boolean requireMinMaxValues,
359             boolean requireMinValuesToBeZero,
360             boolean requireZeroToBeContainedInMinMaxRanges,
361             boolean possiblyDependentOnHvacPowerOn,
362             boolean verifyErrorStates,
363             int powerPropagationDelayMs,
364             ImmutableSet<String> readPermissions,
365             ImmutableList<ImmutableSet<String>> writePermissions) {
366         assertWithMessage("Must set car property manager").that(carPropertyManager).isNotNull();
367         mCarPropertyManager = carPropertyManager;
368         mPropertyId = propertyId;
369         mPropertyName = VehiclePropertyIds.toString(propertyId);
370         mAllowedAccessModes = allowedAccessModes;
371         mAreaType = areaType;
372         mChangeMode = changeMode;
373         mPropertyType = propertyType;
374         mRequiredProperty = requiredProperty;
375         mConfigArrayVerifier = configArrayVerifier;
376         mCarPropertyValueVerifier = carPropertyValueVerifier;
377         mAreaIdsVerifier = areaIdsVerifier;
378         mCarPropertyConfigVerifier = carPropertyConfigVerifier;
379         mDependentOnPropertyId = dependentPropertyId;
380         mDependentOnPropertyPermissions = dependentOnPropertyPermissions;
381         mPossibleConfigArrayValues = possibleConfigArrayValues;
382         mEnumIsBitMap = enumIsBitMap;
383         mAllPossibleEnumValues = allPossibleEnumValues;
384         mAllPossibleUnwritableValues = allPossibleUnwritableValues;
385         mAllPossibleUnavailableValues = allPossibleUnavailableValues;
386         mRequirePropertyValueToBeInConfigArray = requirePropertyValueToBeInConfigArray;
387         mVerifySetterWithConfigArrayValues = verifySetterWithConfigArrayValues;
388         mRequireMinMaxValues = requireMinMaxValues;
389         mRequireMinValuesToBeZero = requireMinValuesToBeZero;
390         mRequireZeroToBeContainedInMinMaxRanges = requireZeroToBeContainedInMinMaxRanges;
391         mPossiblyDependentOnHvacPowerOn = possiblyDependentOnHvacPowerOn;
392         mVerifyErrorStates = verifyErrorStates;
393         mPowerPropagationDelayMs = powerPropagationDelayMs;
394         mReadPermissions = readPermissions;
395         mWritePermissions = writePermissions;
396         mPropertyToAreaIdValues = new SparseArray<>();
397         mVerifierContext = new VerifierContext(carPropertyManager);
398     }
399 
400     /** Gets a new builder for the verifier. */
newBuilder( int propertyId, int access, int areaType, int changeMode, Class<T> propertyType)401     public static <T> Builder<T> newBuilder(
402             int propertyId, int access, int areaType, int changeMode, Class<T> propertyType) {
403         ImmutableSet.Builder<Integer> allowedAccessModesBuilder = new ImmutableSet.Builder<>();
404         allowedAccessModesBuilder.add(access);
405         if (access == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE) {
406             allowedAccessModesBuilder.add(CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ);
407         }
408         return new Builder<>(
409                 propertyId, allowedAccessModesBuilder.build(), areaType, changeMode, propertyType);
410     }
411 
412     /**
413      * Gets a new default verifier builder for the property Id.
414      *
415      * <p>The verifier verifies basic information, including access mode, area type, change mode
416      * property type and permissions.
417      */
newDefaultBuilder(int propertyId)418     public static <T> Builder<T> newDefaultBuilder(int propertyId) {
419         var vehiclePropertyIdInfo = CAR_SVC_PROPS_PARSER.getVehiclePropertyIdInfo(propertyId);
420         var builder =
421                 new Builder<>(
422                         propertyId,
423                         vehiclePropertyIdInfo.allowedAccessModes,
424                         vehiclePropertyIdInfo.areaType,
425                         vehiclePropertyIdInfo.changeMode,
426                         (Class<T>) vehiclePropertyIdInfo.propertyType);
427         for (String readPermission : vehiclePropertyIdInfo.readPermissions) {
428             builder.addReadPermission(readPermission);
429         }
430         for (ImmutableSet<String> allOfWritePermission : vehiclePropertyIdInfo.writePermissions) {
431             builder.addWritePermission(allOfWritePermission);
432         }
433         return builder;
434     }
435 
436     /** Gets the ID of the property. */
getPropertyId()437     public int getPropertyId() {
438         return mPropertyId;
439     }
440 
441     /** Gets the name for the property. */
getPropertyName()442     public String getPropertyName() {
443         return mPropertyName;
444     }
445 
446     /** Gets the default value based on the type. */
447     @Nullable
getDefaultValue(Class<U> clazz)448     public static <U> U getDefaultValue(Class<U> clazz) {
449         if (clazz == Boolean.class) {
450             return (U) Boolean.TRUE;
451         }
452         if (clazz == Integer.class) {
453             return (U) (Integer) 2;
454         }
455         if (clazz == Float.class) {
456             return (U) (Float) 2.f;
457         }
458         if (clazz == Long.class) {
459             return (U) (Long) 2L;
460         }
461         if (clazz == Integer[].class) {
462             return (U) new Integer[] {2};
463         }
464         if (clazz == Float[].class) {
465             return (U) new Float[] {2.f};
466         }
467         if (clazz == Long[].class) {
468             return (U) new Long[] {2L};
469         }
470         if (clazz == String.class) {
471             return (U) new String("test");
472         }
473         if (clazz == byte[].class) {
474             return (U) new byte[] {(byte) 0xbe, (byte) 0xef};
475         }
476         return null;
477     }
478 
accessToString(int access)479     private static String accessToString(int access) {
480         switch (access) {
481             case CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_NONE:
482                 return "VEHICLE_PROPERTY_ACCESS_NONE";
483             case CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ:
484                 return "VEHICLE_PROPERTY_ACCESS_READ";
485             case CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE:
486                 return "VEHICLE_PROPERTY_ACCESS_WRITE";
487             case CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE:
488                 return "VEHICLE_PROPERTY_ACCESS_READ_WRITE";
489             default:
490                 return Integer.toString(access);
491         }
492     }
493 
accessSetToString(Set<Integer> accessSet)494     private static String accessSetToString(Set<Integer> accessSet) {
495         return accessSet.stream()
496                 .map(access -> accessToString(access))
497                 .collect(Collectors.joining(", "));
498     }
499 
areaTypeToString(int areaType)500     private static String areaTypeToString(int areaType) {
501         switch (areaType) {
502             case VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL:
503                 return "VEHICLE_AREA_TYPE_GLOBAL";
504             case VehicleAreaType.VEHICLE_AREA_TYPE_WINDOW:
505                 return "VEHICLE_AREA_TYPE_WINDOW";
506             case VehicleAreaType.VEHICLE_AREA_TYPE_DOOR:
507                 return "VEHICLE_AREA_TYPE_DOOR";
508             case VehicleAreaType.VEHICLE_AREA_TYPE_MIRROR:
509                 return "VEHICLE_AREA_TYPE_MIRROR";
510             case VehicleAreaType.VEHICLE_AREA_TYPE_SEAT:
511                 return "VEHICLE_AREA_TYPE_SEAT";
512             case VehicleAreaType.VEHICLE_AREA_TYPE_WHEEL:
513                 return "VEHICLE_AREA_TYPE_WHEEL";
514             case VehicleAreaType.VEHICLE_AREA_TYPE_VENDOR:
515                 return "VEHICLE_AREA_TYPE_VENDOR";
516             default:
517                 return Integer.toString(areaType);
518         }
519     }
520 
changeModeToString(int changeMode)521     private static String changeModeToString(int changeMode) {
522         switch (changeMode) {
523             case CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_STATIC:
524                 return "VEHICLE_PROPERTY_CHANGE_MODE_STATIC";
525             case CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE:
526                 return "VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE";
527             case CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS:
528                 return "VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS";
529             default:
530                 return Integer.toString(changeMode);
531         }
532     }
533 
534     /**
535      * Gets the car property config for the current property or reads from cache if already cached.
536      *
537      * <p>Note that we statically cache all the property configs using the shell permission.
538      */
getCarPropertyConfig()539     public @Nullable CarPropertyConfig<T> getCarPropertyConfig() {
540         return getCarPropertyConfig(/* useCache= */ true);
541     }
542 
543     /**
544      * Gets the car property config for the current property.
545      *
546      * @param useCache Whether to use a local cache that prefetched all the configs using shell
547      *     permission.
548      */
getCarPropertyConfig(boolean useCache)549     public @Nullable CarPropertyConfig<T> getCarPropertyConfig(boolean useCache) {
550         return (CarPropertyConfig<T>) getCarPropertyConfig(mPropertyId, useCache);
551     }
552 
getCarPropertyConfig(int propertyId)553     private @Nullable CarPropertyConfig<?> getCarPropertyConfig(int propertyId) {
554         return getCarPropertyConfig(propertyId, /* useCache= */ true);
555     }
556 
getCarPropertyConfig(int propertyId, boolean useCache)557     private @Nullable CarPropertyConfig<?> getCarPropertyConfig(int propertyId, boolean useCache) {
558         if (!useCache) {
559             return mCarPropertyManager.getCarPropertyConfig(propertyId);
560         }
561 
562         if (!sIsCarPropertyConfigsCached) {
563             try (PermissionContext p =
564                     TestApis.permissions()
565                             .withPermission(
566                                     TestApis.permissions()
567                                             .adoptablePermissions()
568                                             .toArray(new String[0]))) {
569                 var configs = mCarPropertyManager.getPropertyList();
570                 for (int i = 0; i < configs.size(); i++) {
571                     sCachedCarPropertyConfigs.put(configs.get(i).getPropertyId(), configs.get(i));
572                 }
573             }
574             sIsCarPropertyConfigsCached = true;
575         }
576         return sCachedCarPropertyConfigs.get(propertyId);
577     }
578 
579     /** Returns whether the property is supported. */
isSupported()580     public boolean isSupported() {
581         return getCarPropertyConfig() != null;
582     }
583 
584     /** Gets all verification steps. */
getAllSteps()585     public static ImmutableList<String> getAllSteps() {
586         return ImmutableList.of(
587                 STEP_VERIFY_PROPERTY_CONFIG,
588                 STEP_VERIFY_IS_PROPERTY_AVAILABLE,
589                 STEP_VERIFY_PERMISSION_NOT_GRANTED_EXCEPTION,
590                 STEP_VERIFY_READ_APIS_GET_PROPERTY_SYNC,
591                 STEP_VERIFY_READ_APIS_GET_PROPERTY_ASYNC,
592                 STEP_VERIFY_READ_APIS_SUBSCRIBE,
593                 STEP_VERIFY_READ_APIS_GET_MIN_MAX_SUPPORTED_VALUE,
594                 STEP_VERIFY_READ_APIS_GET_SUPPORTED_VALUES_LIST,
595                 STEP_VERIFY_READ_APIS_REG_UNREG_SUPPORTED_VALUES_CHANGE,
596                 STEP_VERIFY_READ_APIS_DISABLE_ADAS_FEATURE_VERIFY_STATE,
597                 STEP_VERIFY_WRITE_APIS_SET_PROPERTY_SYNC,
598                 STEP_VERIFY_WRITE_APIS_SET_PROPERTY_ASYNC,
599                 STEP_VERIFY_WRITE_APIS_DISABLE_ADAS_FEATURE_VERIFY_STATE,
600                 STEP_VERIFY_READ_APIS_DISABLE_HVAC_GET_NOT_AVAILABLE,
601                 STEP_VERIFY_WRITE_APIS_DISABLE_HVAC_SET_NOT_AVAILABLE,
602                 STEP_VERIFY_READ_PERMISSION_CANNOT_WRITE,
603                 STEP_VERIFY_WRITE_PERMISSION_CANNOT_READ);
604     }
605 
606     /** Runs various verifications on the property. */
verify()607     public void verify() {
608         verify(null);
609     }
610 
611     /** Runs a specific verification step. */
verify(String step, @Nullable Class<?> exceptedExceptionClass)612     public void verify(String step, @Nullable Class<?> exceptedExceptionClass) {
613         if (step.equals(STEP_VERIFY_PROPERTY_CONFIG)) {
614             verifyConfig();
615             return;
616         }
617         if (step.equals(STEP_VERIFY_IS_PROPERTY_AVAILABLE)) {
618             verifyIsPropertyAvailable();
619             return;
620         }
621         assumeTrue("Property: " + getPropertyName() + " is not supported", isSupported());
622         if (step.equals(STEP_VERIFY_PERMISSION_NOT_GRANTED_EXCEPTION)) {
623             verifyPermissionNotGrantedException();
624         } else if (step.startsWith(STEP_VERIFY_READ_APIS_PREFIX)) {
625             verifyReadApis(step, exceptedExceptionClass);
626         } else if (step.startsWith(STEP_VERIFY_WRITE_APIS_PREFIX)) {
627             verifyWriteApis(step, exceptedExceptionClass);
628         } else if (step.equals(STEP_VERIFY_READ_PERMISSION_CANNOT_WRITE)) {
629             verifyReadPermissionCannotWrite();
630         } else if (step.equals(STEP_VERIFY_WRITE_PERMISSION_CANNOT_READ)) {
631             verifyWritePermissionCannotRead();
632         } else {
633             throw new IllegalStateException("Unknown step: " + step);
634         }
635     }
636 
637     /**
638      * Runs all verification steps on the property with exceptions expected.
639      *
640      * @param exceptedExceptionClass The exception class expected for reading/writing the property.
641      */
verify(@ullable Class<?> exceptedExceptionClass)642     public void verify(@Nullable Class<?> exceptedExceptionClass) {
643         verifySteps(getAllSteps(), exceptedExceptionClass);
644     }
645 
verifySteps(List<String> steps, @Nullable Class<?> exceptedExceptionClass)646     private void verifySteps(List<String> steps, @Nullable Class<?> exceptedExceptionClass) {
647         for (int i = 0; i < steps.size(); i++) {
648             try {
649                 verify(steps.get(i), exceptedExceptionClass);
650             } catch (AssumptionViolatedException e) {
651                 if (steps.get(i).equals(STEP_VERIFY_PROPERTY_CONFIG)) {
652                     // If the assumption fails for verifying config. It means the property is not
653                     // supported and we should not continue the rest of the steps.
654                     throw e;
655                 } else {
656                     // Otherwise, we allow one step to be skipped.
657                 }
658             }
659         }
660     }
661 
verifyGetPropertyWhenConfigIsNull(ImmutableSet<String> allPermissions)662     private void verifyGetPropertyWhenConfigIsNull(ImmutableSet<String> allPermissions) {
663         if (!isAtLeastU()) {
664             assertWithMessage(
665                             "CarPropertyManager.getProperty must return null if the property is "
666                                     + "not supported for propertyId: "
667                                     + mPropertyName)
668                     .that(mCarPropertyManager.getProperty(mPropertyId, /* areaId= */ 0))
669                     .isNull();
670             return;
671         }
672 
673         String errorMsg =
674                 "CarPropertyManager.getProperty must throw IllegalArgumentException if "
675                         + "the property is not supported for propertyId: "
676                         + mPropertyName;
677 
678         Exception e =
679                 assertThrows(
680                         errorMsg,
681                         Exception.class,
682                         () -> mCarPropertyManager.getProperty(mPropertyId, /* areaId= */ 0));
683         if (e.getClass() == SecurityException.class) {
684             fail(
685                     "CarPropertyManager.getProperty must throw IllegalArgumentException if the"
686                         + " property is not supported, actually got SecurityException, test may not"
687                         + " have correct permission granted. PropertyId: "
688                             + mPropertyName
689                             + ". Requested permissions: "
690                             + allPermissions);
691             return;
692         }
693         assertWithMessage(errorMsg).that(e.getClass()).isEqualTo(IllegalArgumentException.class);
694     }
695 
verifySetPropertyWhenConfigIsNull(ImmutableSet<String> allPermissions)696     private void verifySetPropertyWhenConfigIsNull(ImmutableSet<String> allPermissions) {
697         String errorMsg =
698                 "CarPropertyManager.setProperty must throw IllegalArgumentException if "
699                         + "the property is not supported for propertyId: "
700                         + mPropertyName;
701 
702         Exception e =
703                 assertThrows(
704                         errorMsg,
705                         Exception.class,
706                         () ->
707                                 mCarPropertyManager.setProperty(
708                                         mPropertyType,
709                                         mPropertyId,
710                                         /* areaId= */ 0,
711                                         getDefaultValue(mPropertyType)));
712 
713         if (e.getClass() == SecurityException.class) {
714             fail(
715                     "CarPropertyManager.setProperty must throw IllegalArgumentException if the"
716                         + " property is not supported, actually got SecurityException, test may not"
717                         + " have correct permission granted. PropertyId: "
718                             + mPropertyName
719                             + ". Requested permissions: "
720                             + allPermissions);
721             return;
722         }
723         assertWithMessage(errorMsg).that(e.getClass()).isEqualTo(IllegalArgumentException.class);
724     }
725 
726     /** Verifies the configuration for the property. */
verifyConfig()727     public void verifyConfig() {
728         ImmutableSet.Builder<String> permissionsBuilder = ImmutableSet.<String>builder();
729         for (ImmutableSet<String> writePermissions : mWritePermissions) {
730             permissionsBuilder.addAll(writePermissions);
731         }
732         ImmutableSet<String> allPermissions = permissionsBuilder.addAll(mReadPermissions).build();
733 
734         runWithShellPermissionIdentity(
735                 () -> {
736                     CarPropertyConfig<T> carPropertyConfig =
737                             getCarPropertyConfig(/* useCache= */ false);
738                     if (carPropertyConfig == null) {
739                         // The property is not supported.
740                         boolean allAllowedAccessCanRead = true;
741                         boolean allAllowedAccessCanWrite = true;
742                         for (int allowedAccessMode : mAllowedAccessModes) {
743                             if (!canRead(allowedAccessMode)) {
744                                 allAllowedAccessCanRead = false;
745                             }
746                             if (!canWrite(allowedAccessMode)) {
747                                 allAllowedAccessCanWrite = false;
748                             }
749                         }
750                         if (allAllowedAccessCanRead) {
751                             verifyGetPropertyWhenConfigIsNull(allPermissions);
752                         }
753                         if (allAllowedAccessCanWrite) {
754                             verifySetPropertyWhenConfigIsNull(allPermissions);
755                         }
756                     }
757 
758                     if (mRequiredProperty) {
759                         assertWithMessage("Must support " + mPropertyName)
760                                 .that(isSupported())
761                                 .isTrue();
762                     } else {
763                         assumeThat(
764                                 "Skipping "
765                                         + mPropertyName
766                                         + " CTS test because the property is not supported on "
767                                         + "this vehicle",
768                                 carPropertyConfig,
769                                 Matchers.notNullValue());
770                     }
771 
772                     verifyCarPropertyConfig();
773                 },
774                 allPermissions.toArray(new String[0]));
775     }
776 
777     /** Verifies that caller can call read APIs with read permission. */
verifyReadApis(String step, Class<?> exceptedExceptionClass)778     private void verifyReadApis(String step, Class<?> exceptedExceptionClass) {
779         for (String readPermission : mReadPermissions) {
780             verifyReadPermissionGivesAccessToReadApis(step, readPermission, exceptedExceptionClass);
781         }
782     }
783 
784     /** Verifies that caller can call write APIs with write permission. */
verifyWriteApis(String step, Class<?> exceptedExceptionClass)785     private void verifyWriteApis(String step, Class<?> exceptedExceptionClass) {
786         for (ImmutableSet<String> writePermissions : mWritePermissions) {
787             verifyWritePermissionsGiveAccessToWriteApis(
788                     step, writePermissions, mReadPermissions, exceptedExceptionClass);
789         }
790     }
791 
792     /** Verifies that caller cannot call write APIs with only read permissions. */
verifyReadPermissionCannotWrite()793     private void verifyReadPermissionCannotWrite() {
794         CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig();
795         for (String readPermission : mReadPermissions) {
796             if (AREA_ID_CONFIG_ACCESS_FLAG) {
797                 for (int areaId : carPropertyConfig.getAreaIds()) {
798                     if (canWrite(carPropertyConfig, areaId)) {
799                         verifyReadPermissionCannotWrite(readPermission, mWritePermissions, areaId);
800                     }
801                 }
802             } else if (canWrite(carPropertyConfig.getAccess())) {
803                 verifyReadPermissionCannotWrite(
804                         readPermission, mWritePermissions, carPropertyConfig.getAreaIds()[0]);
805             }
806         }
807     }
808 
809     /** Verifies that caller cannot call read APIs with only write permissions. */
verifyWritePermissionCannotRead()810     private void verifyWritePermissionCannotRead() {
811         CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig();
812         for (ImmutableSet<String> writePermissions : mWritePermissions) {
813             if (AREA_ID_CONFIG_ACCESS_FLAG) {
814                 for (int areaId : carPropertyConfig.getAreaIds()) {
815                     if (canRead(carPropertyConfig, areaId)) {
816                         verifyWritePermissionsCannotRead(
817                                 writePermissions, mReadPermissions, areaId);
818                     }
819                     if (canWrite(carPropertyConfig, areaId) && writePermissions.size() > 1) {
820                         verifyIndividualWritePermissionsCannotWrite(writePermissions, areaId);
821                     }
822                 }
823             } else {
824                 int areaId = carPropertyConfig.getAreaIds()[0];
825                 if (canRead(carPropertyConfig.getAccess())) {
826                     verifyWritePermissionsCannotRead(writePermissions, mReadPermissions, areaId);
827                 }
828                 if (canWrite(carPropertyConfig.getAccess()) && writePermissions.size() > 1) {
829                     verifyIndividualWritePermissionsCannotWrite(writePermissions, areaId);
830                 }
831             }
832         }
833     }
834 
hasWritePermissions(ImmutableList<ImmutableSet<String>> writePermissions)835     private boolean hasWritePermissions(ImmutableList<ImmutableSet<String>> writePermissions) {
836         for (ImmutableSet<String> writePermissionSet : writePermissions) {
837             boolean result = true;
838             for (String permission : writePermissionSet) {
839                 if (mContext.checkSelfPermission(permission) != PERMISSION_GRANTED) {
840                     result = false;
841                     break;
842                 }
843             }
844             if (result) {
845                 return true;
846             }
847         }
848         return false;
849     }
850 
verifyReadPermissionCannotWrite( String readPermission, ImmutableList<ImmutableSet<String>> writePermissions, int areaId)851     private void verifyReadPermissionCannotWrite(
852             String readPermission,
853             ImmutableList<ImmutableSet<String>> writePermissions,
854             int areaId) {
855         // If the read permission is the same as the write permission and the property does not
856         // require any other write permissions we skip this permission.
857         for (ImmutableSet<String> writePermissionSet : writePermissions) {
858             if (writePermissionSet.size() == 1 && writePermissionSet.contains(readPermission)) {
859                 return;
860             }
861         }
862         // It is possible that the caller has the write permissions without adopting the shell
863         // identity. In this case, we cannot revoke the write permission so we cannot test
864         // setProperty without write permissions.
865         if (hasWritePermissions(writePermissions)) {
866             return;
867         }
868         runWithShellPermissionIdentity(
869                 () -> {
870                     assertThrows(
871                             mPropertyName
872                                     + " - property ID: "
873                                     + mPropertyId
874                                     + " should not be able to be written to without write"
875                                     + " permissions.",
876                             SecurityException.class,
877                             () ->
878                                     mCarPropertyManager.setProperty(
879                                             mPropertyType,
880                                             mPropertyId,
881                                             areaId,
882                                             getDefaultValue(mPropertyType)));
883                 },
884                 readPermission);
885     }
886 
verifyReadPermissionGivesAccessToReadApis( String step, String readPermission, Class<?> exceptedExceptionClass)887     private void verifyReadPermissionGivesAccessToReadApis(
888             String step, String readPermission, Class<?> exceptedExceptionClass) {
889         if (step.equals(STEP_VERIFY_READ_APIS_DISABLE_ADAS_FEATURE_VERIFY_STATE)) {
890             assumeTrue("Not an ADAS property", mDependentOnPropertyId.isPresent());
891             disableAdasFeatureIfAdasStatePropertyAndVerify(
892                     ImmutableSet.<String>builder()
893                             .add(readPermission)
894                             .addAll(mDependentOnPropertyPermissions)
895                             .build()
896                             .toArray(new String[0]),
897                     /* verifySet= */ false);
898             return;
899         }
900 
901         try {
902             enableAdasFeatureIfAdasStateProperty();
903             runWithShellPermissionIdentity(
904                     () -> {
905                         assertThat(getCarPropertyConfig(/* useCache= */ false)).isNotNull();
906                         turnOnHvacPowerIfHvacPowerDependent();
907                         if (step.equals(STEP_VERIFY_READ_APIS_GET_PROPERTY_SYNC)) {
908                             verifyCarPropertyValueGetter();
909                             if (exceptedExceptionClass != null) {
910                                 assertWithMessage(
911                                                 "Expected "
912                                                         + sExceptionClassOnGet
913                                                         + " to be of type "
914                                                         + exceptedExceptionClass)
915                                         .that(sExceptionClassOnGet)
916                                         .isEqualTo(exceptedExceptionClass);
917                             }
918                             return;
919                         }
920                         if (exceptedExceptionClass != null) {
921                             return;
922                         }
923 
924                         if (step.equals(STEP_VERIFY_READ_APIS_GET_PROPERTY_ASYNC)) {
925                             verifyGetPropertiesAsync();
926                         }
927 
928                         if (step.equals(STEP_VERIFY_READ_APIS_SUBSCRIBE)) {
929                             verifyCarPropertyValueCallback();
930                         }
931 
932                         if (step.equals(STEP_VERIFY_READ_APIS_GET_MIN_MAX_SUPPORTED_VALUE)
933                                 && CAR_PROPERTY_SUPPORTED_VALUE_FLAG) {
934                             verifyGetMinMaxSupportedValue();
935                         }
936 
937                         if (step.equals(STEP_VERIFY_READ_APIS_GET_SUPPORTED_VALUES_LIST)
938                                 && CAR_PROPERTY_SUPPORTED_VALUE_FLAG) {
939                             verifyGetSupportedValuesList();
940                         }
941 
942                         if (step.equals(STEP_VERIFY_READ_APIS_REG_UNREG_SUPPORTED_VALUES_CHANGE)
943                                 && CAR_PROPERTY_SUPPORTED_VALUE_FLAG) {
944                             verifyRegisterUnregisterSupportedValuesChangeCallback();
945                         }
946 
947                         if (step.equals(STEP_VERIFY_READ_APIS_DISABLE_HVAC_GET_NOT_AVAILABLE)) {
948                             assumeTrue(
949                                     "Not depending on HVAC power", mPossiblyDependentOnHvacPowerOn);
950                             ImmutableSet<Integer> areaIdsTurnedOff =
951                                     turnOffHvacPowerIfHvacPowerDependent();
952                             if (!areaIdsTurnedOff.isEmpty()) {
953                                 verifyGetNotAvailable(areaIdsTurnedOff);
954                             }
955                         }
956                     },
957                     readPermission);
958         } finally {
959             // Restore all property values even if test fails.
960             runWithShellPermissionIdentity(
961                     () -> {
962                         restoreInitialValues();
963                     },
964                     ImmutableSet.<String>builder()
965                             .add(readPermission)
966                             .addAll(mDependentOnPropertyPermissions)
967                             .build()
968                             .toArray(new String[0]));
969         }
970     }
971 
hasReadPermissions(ImmutableSet<String> allReadPermissions)972     private boolean hasReadPermissions(ImmutableSet<String> allReadPermissions) {
973         for (String permission : allReadPermissions) {
974             if (mContext.checkSelfPermission(permission) == PERMISSION_GRANTED) {
975                 return true;
976             }
977         }
978         return false;
979     }
980 
assertGetPropertyThrowsException( String msg, Class<? extends Throwable> exceptionClass, int propertyId, int areaId)981     private void assertGetPropertyThrowsException(
982             String msg, Class<? extends Throwable> exceptionClass, int propertyId, int areaId) {
983         assertThrows(
984                 msg, exceptionClass, () -> mCarPropertyManager.getProperty(propertyId, areaId));
985         assertThrows(
986                 msg,
987                 exceptionClass,
988                 () -> mCarPropertyManager.getBooleanProperty(propertyId, areaId));
989         assertThrows(
990                 msg, exceptionClass, () -> mCarPropertyManager.getIntProperty(propertyId, areaId));
991         assertThrows(
992                 msg,
993                 exceptionClass,
994                 () -> mCarPropertyManager.getFloatProperty(propertyId, areaId));
995         assertThrows(
996                 msg,
997                 exceptionClass,
998                 () -> mCarPropertyManager.getIntArrayProperty(propertyId, areaId));
999     }
1000 
verifyWritePermissionsCannotRead( ImmutableSet<String> writePermissions, ImmutableSet<String> allReadPermissions, int areaId)1001     private void verifyWritePermissionsCannotRead(
1002             ImmutableSet<String> writePermissions,
1003             ImmutableSet<String> allReadPermissions,
1004             int areaId) {
1005         // If there is any write permission that is also a read permission we skip the permissions.
1006         if (!Collections.disjoint(writePermissions, allReadPermissions)) {
1007             return;
1008         }
1009         // It is possible that the caller has the read permissions without adopting the shell
1010         // identity. In this case, we cannot revoke the read permissions so we cannot test
1011         // getProperty without read permissions.
1012         if (hasReadPermissions(allReadPermissions)) {
1013             return;
1014         }
1015         runWithShellPermissionIdentity(
1016                 () -> {
1017                     assertGetPropertyThrowsException(
1018                             mPropertyName
1019                                     + " - property ID: "
1020                                     + mPropertyId
1021                                     + " should not be able to be read without read"
1022                                     + " permissions.",
1023                             SecurityException.class,
1024                             mPropertyId,
1025                             areaId);
1026                     assertThrows(
1027                             mPropertyName
1028                                     + " - property ID: "
1029                                     + mPropertyId
1030                                     + " should not be able to be listened to without read"
1031                                     + " permissions.",
1032                             SecurityException.class,
1033                             () -> verifyCarPropertyValueCallback());
1034                     assertThrows(
1035                             mPropertyName
1036                                     + " - property ID: "
1037                                     + mPropertyId
1038                                     + " should not be able to be read without read"
1039                                     + " permissions.",
1040                             SecurityException.class,
1041                             () -> verifyGetPropertiesAsync());
1042 
1043                     // If the caller only has write permission, registerCallback throws
1044                     // SecurityException.
1045                     assertThrows(
1046                             mPropertyName
1047                                     + " - property ID: "
1048                                     + mPropertyId
1049                                     + " should not be able to be listened to without read"
1050                                     + " permission.",
1051                             SecurityException.class,
1052                             () ->
1053                                     mCarPropertyManager.registerCallback(
1054                                             FAKE_CALLBACK, mPropertyId, 0f));
1055 
1056                     if (isAtLeastV() && Flags.variableUpdateRate()) {
1057                         // For the new API, if the caller does not read permission, it throws
1058                         // SecurityException.
1059                         assertThrows(
1060                                 mPropertyName
1061                                         + " - property ID: "
1062                                         + mPropertyId
1063                                         + " should not be able to be listened to without read"
1064                                         + " permission.",
1065                                 SecurityException.class,
1066                                 () ->
1067                                         mCarPropertyManager.subscribePropertyEvents(
1068                                                 mPropertyId, areaId, FAKE_CALLBACK));
1069                     }
1070                 },
1071                 writePermissions.toArray(new String[0]));
1072     }
1073 
verifyIndividualWritePermissionsCannotWrite( ImmutableSet<String> writePermissions, int areaId)1074     private void verifyIndividualWritePermissionsCannotWrite(
1075             ImmutableSet<String> writePermissions, int areaId) {
1076         // It is possible that the caller has the write permissions without adopting
1077         // the shell identity. In this case, we cannot revoke individual permissions.
1078         if (hasWritePermissions(ImmutableList.of(writePermissions))) {
1079             return;
1080         }
1081 
1082         String writePermissionsNeededString = String.join(", ", writePermissions);
1083         for (String writePermission : writePermissions) {
1084             runWithShellPermissionIdentity(
1085                     () -> {
1086                         assertThat(getCarPropertyConfig(/* useCache= */ false)).isNull();
1087                         assertThrows(
1088                                 mPropertyName
1089                                         + " - property ID: "
1090                                         + mPropertyId
1091                                         + " should not be able to be written to without all of the"
1092                                         + " following permissions granted: "
1093                                         + writePermissionsNeededString,
1094                                 SecurityException.class,
1095                                 () ->
1096                                         mCarPropertyManager.setProperty(
1097                                                 mPropertyType,
1098                                                 mPropertyId,
1099                                                 areaId,
1100                                                 getDefaultValue(mPropertyType)));
1101                     },
1102                     writePermission);
1103         }
1104     }
1105 
verifyWritePermissionsGiveAccessToWriteApis( String step, ImmutableSet<String> writePermissions, ImmutableSet<String> readPermissions, Class<?> exceptedExceptionClass)1106     private void verifyWritePermissionsGiveAccessToWriteApis(
1107             String step,
1108             ImmutableSet<String> writePermissions,
1109             ImmutableSet<String> readPermissions,
1110             Class<?> exceptedExceptionClass) {
1111         ImmutableSet<String> propertyPermissions =
1112                 ImmutableSet.<String>builder()
1113                         .addAll(writePermissions)
1114                         .addAll(readPermissions)
1115                         .build();
1116 
1117         if (step.equals(STEP_VERIFY_WRITE_APIS_DISABLE_ADAS_FEATURE_VERIFY_STATE)) {
1118             assumeTrue("Not an ADAS property", mDependentOnPropertyId.isPresent());
1119             disableAdasFeatureIfAdasStatePropertyAndVerify(
1120                     propertyPermissions.toArray(new String[0]), /* verifySet= */ true);
1121             return;
1122         }
1123 
1124         try {
1125             // Store the current value before we call enableAdasFeatureIfAdasStateProperty, which
1126             // might change this.
1127             runWithShellPermissionIdentity(
1128                     () -> {
1129                         storeCurrentValues();
1130                     },
1131                     propertyPermissions.toArray(new String[0]));
1132             enableAdasFeatureIfAdasStateProperty();
1133 
1134             runWithShellPermissionIdentity(
1135                     () -> {
1136                         turnOnHvacPowerIfHvacPowerDependent();
1137 
1138                         if (step.equals(STEP_VERIFY_WRITE_APIS_SET_PROPERTY_SYNC)) {
1139                             verifyCarPropertyValueSetter();
1140                             if (exceptedExceptionClass != null) {
1141                                 assertWithMessage(
1142                                                 "Expected "
1143                                                         + sExceptionClassOnSet
1144                                                         + " to be of type "
1145                                                         + exceptedExceptionClass)
1146                                         .that(sExceptionClassOnSet)
1147                                         .isEqualTo(exceptedExceptionClass);
1148                             }
1149                         }
1150 
1151                         if (step.equals(STEP_VERIFY_WRITE_APIS_SET_PROPERTY_ASYNC)
1152                                 && exceptedExceptionClass == null) {
1153                             verifySetPropertiesAsync();
1154                         }
1155                         if (step.equals(STEP_VERIFY_WRITE_APIS_DISABLE_HVAC_SET_NOT_AVAILABLE)) {
1156                             assumeTrue(
1157                                     "Not depending on HVAC power", mPossiblyDependentOnHvacPowerOn);
1158                             ImmutableSet<Integer> areaIdsTurnedOff =
1159                                     turnOffHvacPowerIfHvacPowerDependent();
1160                             if (!areaIdsTurnedOff.isEmpty()) {
1161                                 verifySetNotAvailable(areaIdsTurnedOff);
1162                             }
1163                         }
1164                     },
1165                     propertyPermissions.toArray(new String[0]));
1166         } finally {
1167             // Restore all property values even if test fails.
1168             runWithShellPermissionIdentity(
1169                     () -> {
1170                         restoreInitialValues();
1171                     },
1172                     ImmutableSet.<String>builder()
1173                             .addAll(propertyPermissions)
1174                             .addAll(mDependentOnPropertyPermissions)
1175                             .build()
1176                             .toArray(new String[0]));
1177         }
1178     }
1179 
turnOnHvacPowerIfHvacPowerDependent()1180     private void turnOnHvacPowerIfHvacPowerDependent() {
1181         if (!mPossiblyDependentOnHvacPowerOn) {
1182             return;
1183         }
1184 
1185         CarPropertyConfig<Boolean> hvacPowerOnCarPropertyConfig =
1186                 (CarPropertyConfig<Boolean>) getCarPropertyConfig(VehiclePropertyIds.HVAC_POWER_ON);
1187         if (hvacPowerOnCarPropertyConfig == null
1188                 || !hvacPowerOnCarPropertyConfig.getConfigArray().contains(mPropertyId)) {
1189             return;
1190         }
1191 
1192         storeCurrentValuesForProperty(hvacPowerOnCarPropertyConfig);
1193         // Turn the power on for all supported HVAC area IDs.
1194         setBooleanPowerPropertyInAllAreaIds(
1195                 hvacPowerOnCarPropertyConfig, /* setValue: */ Boolean.TRUE);
1196     }
1197 
turnOffHvacPowerIfHvacPowerDependent()1198     private ImmutableSet<Integer> turnOffHvacPowerIfHvacPowerDependent() {
1199         CarPropertyConfig<Boolean> hvacPowerOnCarPropertyConfig =
1200                 (CarPropertyConfig<Boolean>) getCarPropertyConfig(VehiclePropertyIds.HVAC_POWER_ON);
1201         if (hvacPowerOnCarPropertyConfig == null
1202                 || !hvacPowerOnCarPropertyConfig.getConfigArray().contains(mPropertyId)) {
1203             return ImmutableSet.of();
1204         }
1205 
1206         // Turn the power off for all supported HVAC area IDs.
1207         return setBooleanPowerPropertyInAllAreaIds(
1208                 hvacPowerOnCarPropertyConfig, /* setValue: */ Boolean.FALSE);
1209     }
1210 
1211     /** Enables the ADAS feature if the property is an ADAS property. */
enableAdasFeatureIfAdasStateProperty()1212     public void enableAdasFeatureIfAdasStateProperty() {
1213         if (!mDependentOnPropertyId.isPresent()) {
1214             return;
1215         }
1216 
1217         runWithShellPermissionIdentity(
1218                 () -> {
1219                     int adasEnabledPropertyId = mDependentOnPropertyId.get();
1220                     CarPropertyConfig<Boolean> adasEnabledCarPropertyConfig =
1221                             (CarPropertyConfig<Boolean>)
1222                                     getCarPropertyConfig(adasEnabledPropertyId);
1223 
1224                     if (adasEnabledCarPropertyConfig == null
1225                             || !canWrite(
1226                                     getAreaIdAccessOrElseGlobalAccess(
1227                                             adasEnabledCarPropertyConfig, GLOBAL_AREA_ID))) {
1228                         Log.w(
1229                                 TAG,
1230                                 "Cannot enable "
1231                                         + VehiclePropertyIds.toString(adasEnabledPropertyId)
1232                                         + " for testing "
1233                                         + VehiclePropertyIds.toString(mPropertyId)
1234                                         + " because property is either not implemented or READ"
1235                                         + " only. Manually enable if it's not already enabled.");
1236                         return;
1237                     }
1238 
1239                     storeCurrentValuesForProperty(adasEnabledCarPropertyConfig);
1240                     // Enable ADAS feature in all supported area IDs.
1241                     setBooleanPowerPropertyInAllAreaIds(
1242                             adasEnabledCarPropertyConfig, /* setValue: */ Boolean.TRUE);
1243                 },
1244                 mDependentOnPropertyPermissions.toArray(new String[0]));
1245     }
1246 
disableAdasFeatureIfAdasStatePropertyAndVerify( String[] enabledPermissionsList, boolean verifySet)1247     private void disableAdasFeatureIfAdasStatePropertyAndVerify(
1248             String[] enabledPermissionsList, boolean verifySet) {
1249         try {
1250             ImmutableSet<Integer> areaIdsDisabled = disableAdasFeatureIfAdasStateProperty();
1251             if (areaIdsDisabled.isEmpty()) {
1252                 return;
1253             }
1254             runWithShellPermissionIdentity(
1255                     () -> {
1256                         if (mVerifyErrorStates) {
1257                             verifyAdasPropertyErrorState(areaIdsDisabled);
1258                         } else if (verifySet) {
1259                             verifySetNotAvailable(areaIdsDisabled);
1260                         } else {
1261                             verifyGetNotAvailable(areaIdsDisabled);
1262                         }
1263                     },
1264                     enabledPermissionsList);
1265         } finally {
1266             // Restore all property values even if test fails.
1267             runWithShellPermissionIdentity(
1268                     () -> {
1269                         restoreInitialValues();
1270                     },
1271                     mDependentOnPropertyPermissions.toArray(new String[0]));
1272         }
1273     }
1274 
1275     /**
1276      * Disables the ADAS feature if the property is an ADAS property.
1277      *
1278      * @return all area IDs that are disabled
1279      */
disableAdasFeatureIfAdasStateProperty()1280     public ImmutableSet<Integer> disableAdasFeatureIfAdasStateProperty() {
1281         if (!mDependentOnPropertyId.isPresent()) {
1282             return ImmutableSet.of();
1283         }
1284 
1285         ImmutableSet.Builder<Integer> areaIdsDisabled = ImmutableSet.builder();
1286         runWithShellPermissionIdentity(
1287                 () -> {
1288                     int adasEnabledPropertyId = mDependentOnPropertyId.get();
1289                     CarPropertyConfig<Boolean> adasEnabledCarPropertyConfig =
1290                             (CarPropertyConfig<Boolean>)
1291                                     getCarPropertyConfig(adasEnabledPropertyId);
1292 
1293                     if (adasEnabledCarPropertyConfig == null
1294                             || !canWrite(
1295                                     getAreaIdAccessOrElseGlobalAccess(
1296                                             adasEnabledCarPropertyConfig, GLOBAL_AREA_ID))) {
1297                         return;
1298                     }
1299 
1300                     storeCurrentValuesForProperty(adasEnabledCarPropertyConfig);
1301 
1302                     // Disable ADAS feature in all supported area IDs.
1303                     areaIdsDisabled.addAll(
1304                             setBooleanPowerPropertyInAllAreaIds(
1305                                     adasEnabledCarPropertyConfig, /* setValue: */ Boolean.FALSE));
1306                 },
1307                 mDependentOnPropertyPermissions.toArray(new String[0]));
1308         return areaIdsDisabled.build();
1309     }
1310 
1311     /** Stores the property's current values for all areas so that they can be restored later. */
storeCurrentValues()1312     public void storeCurrentValues() {
1313         storeCurrentValuesForProperty(getCarPropertyConfig());
1314     }
1315 
storeCurrentValuesForProperty(CarPropertyConfig<U> carPropertyConfig)1316     private <U> void storeCurrentValuesForProperty(CarPropertyConfig<U> carPropertyConfig) {
1317         SparseArray<U> areaIdToInitialValue =
1318                 getInitialValuesByAreaId(carPropertyConfig, mCarPropertyManager);
1319         if (areaIdToInitialValue == null || areaIdToInitialValue.size() == 0) {
1320             return;
1321         }
1322         var propertyId = carPropertyConfig.getPropertyId();
1323         if (mPropertyToAreaIdValues.contains(propertyId)) {
1324             throw new IllegalStateException(
1325                     "The property: "
1326                             + VehiclePropertyIds.toString(propertyId)
1327                             + " already has a stored value");
1328         }
1329         mStoredProperties.add(propertyId);
1330         for (int i = 0; i < areaIdToInitialValue.size(); i++) {
1331             Log.i(
1332                     TAG,
1333                     "Storing initial value for property:"
1334                             + VehiclePropertyIds.toString(propertyId)
1335                             + " at area ID: "
1336                             + areaIdToInitialValue.keyAt(i)
1337                             + " to "
1338                             + areaIdToInitialValue.valueAt(i));
1339         }
1340         mPropertyToAreaIdValues.put(propertyId, areaIdToInitialValue);
1341     }
1342 
restoreInitialValue(int propertyId)1343     private void restoreInitialValue(int propertyId) {
1344         var carPropertyConfig = getCarPropertyConfig(propertyId);
1345         SparseArray<?> areaIdToInitialValue = mPropertyToAreaIdValues.get(propertyId);
1346 
1347         if (areaIdToInitialValue == null || carPropertyConfig == null) {
1348             Log.w(
1349                     TAG,
1350                     "No stored values for "
1351                             + VehiclePropertyIds.toString(propertyId)
1352                             + " to restore to, ignore");
1353             return;
1354         }
1355 
1356         restoreInitialValuesByAreaId(carPropertyConfig, mCarPropertyManager, areaIdToInitialValue);
1357     }
1358 
1359     /**
1360      * Restore the property's and dependent properties values to original values stored by previous
1361      * {@link #storeCurrentValues}.
1362      *
1363      * <p>Do nothing if no stored current values are available.
1364      *
1365      * <p>The properties values are restored in the reverse-order as they are stored.
1366      */
restoreInitialValues()1367     public void restoreInitialValues() {
1368         for (int i = mStoredProperties.size() - 1; i >= 0; i--) {
1369             restoreInitialValue(mStoredProperties.get(i));
1370         }
1371         mStoredProperties.clear();
1372         mPropertyToAreaIdValues.clear();
1373     }
1374 
1375     // Get a map storing the property's area Ids to the initial values.
1376     @Nullable
getInitialValuesByAreaId( CarPropertyConfig<U> carPropertyConfig, CarPropertyManager carPropertyManager)1377     private static <U> SparseArray<U> getInitialValuesByAreaId(
1378             CarPropertyConfig<U> carPropertyConfig, CarPropertyManager carPropertyManager) {
1379         if (!AREA_ID_CONFIG_ACCESS_FLAG
1380                 && !(canRead(carPropertyConfig.getAccess())
1381                         && canWrite(carPropertyConfig.getAccess()))) {
1382             return null;
1383         }
1384         SparseArray<U> areaIdToInitialValue = new SparseArray<U>();
1385         int propertyId = carPropertyConfig.getPropertyId();
1386         String propertyName = VehiclePropertyIds.toString(propertyId);
1387         for (int areaId : carPropertyConfig.getAreaIds()) {
1388             if (!(canRead(carPropertyConfig, areaId) && canWrite(carPropertyConfig, areaId))) {
1389                 continue;
1390             }
1391             CarPropertyValue<U> carPropertyValue;
1392             try {
1393                 carPropertyValue = carPropertyManager.getProperty(propertyId, areaId);
1394             } catch (PropertyNotAvailableAndRetryException
1395                     | PropertyNotAvailableException
1396                     | CarInternalErrorException e) {
1397                 Log.w(
1398                         TAG,
1399                         "Failed to get property:"
1400                                 + propertyName
1401                                 + " at area ID: "
1402                                 + areaId
1403                                 + " to save initial car property value. Error: "
1404                                 + e);
1405                 continue;
1406             }
1407             if (carPropertyValue == null) {
1408                 Log.w(
1409                         TAG,
1410                         "Failed to get property:"
1411                                 + propertyName
1412                                 + " at area ID: "
1413                                 + areaId
1414                                 + " to save initial car property value.");
1415                 continue;
1416             }
1417             if (carPropertyValue.getStatus() != CarPropertyValue.STATUS_AVAILABLE) {
1418                 Log.w(
1419                         TAG,
1420                         "Cannot save initial value for property:"
1421                                 + propertyName
1422                                 + " at area ID: "
1423                                 + areaId
1424                                 + " because status: "
1425                                 + carPropertyValue.getStatus());
1426                 continue;
1427             }
1428             areaIdToInitialValue.put(areaId, (U) carPropertyValue.getValue());
1429         }
1430         return areaIdToInitialValue;
1431     }
1432 
1433     /**
1434      * Set a boolean property that represents the power state to a desired value in all supported
1435      * area IDs.
1436      *
1437      * <p>Power properties include {@code HVAC_POWER_ON} and all ADAS ENABLED properties such as
1438      * {@code AUTOMATIC_EMERGENCY_BRAKING_ENABLED}.
1439      *
1440      * @return all area IDs that are set (or already set) to {@code setValue}
1441      */
setBooleanPowerPropertyInAllAreaIds( CarPropertyConfig<Boolean> booleanCarPropertyConfig, Boolean setValue)1442     private ImmutableSet<Integer> setBooleanPowerPropertyInAllAreaIds(
1443             CarPropertyConfig<Boolean> booleanCarPropertyConfig, Boolean setValue) {
1444         ImmutableSet.Builder<Integer> areaIdsUpdated = ImmutableSet.builder();
1445         boolean updateSentToVhal = false;
1446         int propertyId = booleanCarPropertyConfig.getPropertyId();
1447         for (int areaId : booleanCarPropertyConfig.getAreaIds()) {
1448             if (mCarPropertyManager.getBooleanProperty(propertyId, areaId) == setValue) {
1449                 areaIdsUpdated.add(areaId);
1450                 continue;
1451             }
1452             CarPropertyValue<Boolean> updatedValue =
1453                     setPropertyAndWaitForChange(
1454                             mCarPropertyManager, propertyId, Boolean.class, areaId, setValue);
1455             if (updatedValue == null) {
1456                 continue;
1457             }
1458             areaIdsUpdated.add(areaId);
1459             updateSentToVhal = true;
1460         }
1461         if (updateSentToVhal && mPowerPropagationDelayMs != 0) {
1462             Log.i(
1463                     TAG,
1464                     "Setting power property:"
1465                             + VehiclePropertyIds.toString(propertyId)
1466                             + " to value: "
1467                             + setValue
1468                             + " and sleeping for: "
1469                             + mPowerPropagationDelayMs);
1470             // Wait for power status to propagate to dependent properties
1471             SystemClock.sleep(mPowerPropagationDelayMs);
1472             Log.i(TAG, "Completed sleeping for: " + mPowerPropagationDelayMs);
1473         }
1474         return areaIdsUpdated.build();
1475     }
1476 
1477     // Restore the initial values of the property provided by {@code areaIdToInitialValue}.
restoreInitialValuesByAreaId( CarPropertyConfig<?> carPropertyConfig, CarPropertyManager carPropertyManager, SparseArray<?> areaIdToInitialValue)1478     private static void restoreInitialValuesByAreaId(
1479             CarPropertyConfig<?> carPropertyConfig,
1480             CarPropertyManager carPropertyManager,
1481             SparseArray<?> areaIdToInitialValue) {
1482         int propertyId = carPropertyConfig.getPropertyId();
1483         String propertyName = VehiclePropertyIds.toString(propertyId);
1484         for (int i = 0; i < areaIdToInitialValue.size(); i++) {
1485             int areaId = areaIdToInitialValue.keyAt(i);
1486             Object originalValue = areaIdToInitialValue.valueAt(i);
1487             CarPropertyValue<?> currentCarPropertyValue;
1488             try {
1489                 currentCarPropertyValue = carPropertyManager.getProperty(propertyId, areaId);
1490             } catch (PropertyNotAvailableAndRetryException
1491                     | PropertyNotAvailableException
1492                     | CarInternalErrorException e) {
1493                 Log.w(
1494                         TAG,
1495                         "Failed to get property:"
1496                                 + propertyName
1497                                 + " at area ID: "
1498                                 + areaId
1499                                 + " to restore initial car property value. Error: "
1500                                 + e);
1501                 continue;
1502             }
1503             if (currentCarPropertyValue == null) {
1504                 Log.w(
1505                         TAG,
1506                         "Failed to get property:"
1507                                 + propertyName
1508                                 + " at area ID: "
1509                                 + areaId
1510                                 + " to restore initial car property value.");
1511                 continue;
1512             }
1513             if (currentCarPropertyValue.getStatus() != CarPropertyValue.STATUS_AVAILABLE) {
1514                 Log.w(
1515                         TAG,
1516                         "Cannot restore initial value for property:"
1517                                 + propertyName
1518                                 + " at area ID: "
1519                                 + areaId
1520                                 + " because status: "
1521                                 + currentCarPropertyValue.getStatus());
1522                 continue;
1523             }
1524             if (valueEquals(originalValue, currentCarPropertyValue.getValue())) {
1525                 continue;
1526             }
1527             Log.i(
1528                     TAG,
1529                     "Restoring value for: "
1530                             + propertyName
1531                             + " at area ID: "
1532                             + areaId
1533                             + " to "
1534                             + originalValue);
1535             setPropertyAndWaitForChange(
1536                     carPropertyManager, propertyId, Object.class, areaId, originalValue);
1537         }
1538     }
1539 
1540     /**
1541      * Gets the possible values that could be set to.
1542      *
1543      * <p>The values returned here must not cause {@code IllegalArgumentException} for set.
1544      *
1545      * <p>Returns {@code null} or empty array if we don't know possible values.
1546      */
getPossibleValues(int areaId)1547     public @Nullable Collection<T> getPossibleValues(int areaId) {
1548         CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig();
1549         if (Boolean.class.equals(carPropertyConfig.getPropertyType())) {
1550             return (List<T>) List.of(Boolean.TRUE, Boolean.FALSE);
1551         } else if (Integer.class.equals(carPropertyConfig.getPropertyType())) {
1552             return (List<T>) getPossibleIntegerValues(areaId);
1553         } else if (Float.class.equals(carPropertyConfig.getPropertyType())) {
1554             return getPossibleFloatValues(areaId);
1555         }
1556         return null;
1557     }
1558 
isAtLeastU()1559     public static boolean isAtLeastU() {
1560         return Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
1561     }
1562 
isAtLeastV()1563     private static boolean isAtLeastV() {
1564         return Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM;
1565     }
1566 
isAtLeastB()1567     private static boolean isAtLeastB() {
1568         return Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA;
1569     }
1570 
1571     /** Gets the possible values for an integer property. */
getPossibleIntegerValues(int areaId)1572     private List<Integer> getPossibleIntegerValues(int areaId) {
1573         CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig();
1574         List<Integer> possibleValues = new ArrayList<>();
1575         if (mPropertyId == VehiclePropertyIds.HVAC_FAN_DIRECTION) {
1576             int[] availableHvacFanDirections =
1577                     mCarPropertyManager.getIntArrayProperty(
1578                             VehiclePropertyIds.HVAC_FAN_DIRECTION_AVAILABLE, areaId);
1579             for (int i = 0; i < availableHvacFanDirections.length; i++) {
1580                 if (availableHvacFanDirections[i] != CarHvacFanDirection.UNKNOWN) {
1581                     possibleValues.add(availableHvacFanDirections[i]);
1582                 }
1583             }
1584             return possibleValues;
1585         }
1586         if (mVerifySetterWithConfigArrayValues) {
1587             for (Integer value : carPropertyConfig.getConfigArray()) {
1588                 possibleValues.add(value);
1589             }
1590             return possibleValues;
1591         }
1592 
1593         if (!mAllPossibleEnumValues.isEmpty() && isAtLeastU()) {
1594             AreaIdConfig areaIdConfig = carPropertyConfig.getAreaIdConfig(areaId);
1595             for (Integer value : (List<Integer>) areaIdConfig.getSupportedEnumValues()) {
1596                 if ((mAllPossibleUnwritableValues.isEmpty()
1597                                 || !mAllPossibleUnwritableValues.contains(value))
1598                         && (mAllPossibleUnavailableValues.isEmpty()
1599                                 || !mAllPossibleUnavailableValues.contains(value))) {
1600                     possibleValues.add(value);
1601                 }
1602             }
1603         } else {
1604             Integer minValue = (Integer) carPropertyConfig.getMinValue(areaId);
1605             Integer maxValue = (Integer) carPropertyConfig.getMaxValue(areaId);
1606             assertWithMessage(
1607                             "Read-write/Write integer properties should either have a config array"
1608                                 + " with valid set values, a set of supported enums, or valid min"
1609                                 + " and max values set in the CarPropertyConfig. However, the"
1610                                 + " following property has none of these: "
1611                                     + VehiclePropertyIds.toString(mPropertyId))
1612                     .that(minValue != null && maxValue != null)
1613                     .isTrue();
1614             List<Integer> valuesToSet =
1615                     IntStream.rangeClosed(minValue.intValue(), maxValue.intValue())
1616                             .boxed()
1617                             .collect(Collectors.toList());
1618             for (int i = 0; i < valuesToSet.size(); i++) {
1619                 possibleValues.add(valuesToSet.get(i));
1620             }
1621         }
1622         return possibleValues;
1623     }
1624 
1625     /** Gets the possible values for a float property. */
getPossibleFloatValues(int areaId)1626     private Collection<T> getPossibleFloatValues(int areaId) {
1627         ImmutableSet.Builder<Float> possibleValuesBuilder = ImmutableSet.builder();
1628         if (mPropertyId == VehiclePropertyIds.HVAC_TEMPERATURE_SET) {
1629             List<Integer> hvacTempSetConfigArray = getCarPropertyConfig().getConfigArray();
1630             if (!hvacTempSetConfigArray.isEmpty()) {
1631                 // For HVAC_TEMPERATURE_SET, the configArray specifies the supported temperature
1632                 // values for the property. configArray[0] is the lower bound of the supported
1633                 // temperatures in Celsius. configArray[1] is the upper bound of the supported
1634                 // temperatures in Celsius. configArray[2] is the supported temperature increment
1635                 // between the two bounds. All configArray values are Celsius*10 since the
1636                 // configArray is List<Integer> but HVAC_TEMPERATURE_SET is a Float type property.
1637                 for (int possibleHvacTempSetValue = hvacTempSetConfigArray.get(0);
1638                         possibleHvacTempSetValue <= hvacTempSetConfigArray.get(1);
1639                         possibleHvacTempSetValue += hvacTempSetConfigArray.get(2)) {
1640                     possibleValuesBuilder.add((float) possibleHvacTempSetValue / 10.0f);
1641                 }
1642             } else {
1643                 // If the configArray is not specified, then use min/max values.
1644                 Float minValueFloat = (Float) getCarPropertyConfig().getMinValue(areaId);
1645                 Float maxValueFloat = (Float) getCarPropertyConfig().getMaxValue(areaId);
1646                 possibleValuesBuilder.add(minValueFloat);
1647                 possibleValuesBuilder.add(maxValueFloat);
1648             }
1649         } else if (mPropertyId == VehiclePropertyIds.EV_CHARGE_PERCENT_LIMIT) {
1650             List<Integer> evChargePercentLimitConfigArray = getCarPropertyConfig().getConfigArray();
1651             if (!evChargePercentLimitConfigArray.isEmpty()) {
1652                 for (Integer possibleEvChargePercentLimit : evChargePercentLimitConfigArray) {
1653                     possibleValuesBuilder.add(possibleEvChargePercentLimit.floatValue());
1654                 }
1655             } else {
1656                 // If the configArray is not specified, then values between 0 and 100 percent must
1657                 // be supported.
1658                 possibleValuesBuilder.add(0f);
1659                 possibleValuesBuilder.add(100f);
1660             }
1661         } else if (mPropertyId == VehiclePropertyIds.EV_CHARGE_CURRENT_DRAW_LIMIT) {
1662             // First value in the configArray specifies the max current draw allowed by the vehicle.
1663             Integer vehicleMaxCurrentDrawLimit = getCarPropertyConfig().getConfigArray().get(0);
1664             possibleValuesBuilder.add(vehicleMaxCurrentDrawLimit.floatValue());
1665         } else if (mPropertyId == VehiclePropertyIds.RANGE_REMAINING) {
1666             // Test when no range is remaining
1667             possibleValuesBuilder.add(0f);
1668         }
1669         return (Collection<T>) possibleValuesBuilder.build();
1670     }
1671 
verifyCarPropertyValueSetter()1672     private void verifyCarPropertyValueSetter() {
1673         CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig();
1674         if (!AREA_ID_CONFIG_ACCESS_FLAG && !canWrite(carPropertyConfig.getAccess())) {
1675             verifySetPropertyFails(carPropertyConfig.getAreaIds()[0]);
1676             return;
1677         }
1678         if (Boolean.class.equals(carPropertyConfig.getPropertyType())) {
1679             verifyBooleanPropertySetter();
1680         } else if (Integer.class.equals(carPropertyConfig.getPropertyType())) {
1681             verifyIntegerPropertySetter();
1682         } else if (Float.class.equals(carPropertyConfig.getPropertyType())) {
1683             verifyFloatPropertySetter();
1684         } else if (mPropertyId == VehiclePropertyIds.HVAC_TEMPERATURE_VALUE_SUGGESTION) {
1685             verifyHvacTemperatureValueSuggestionSetter();
1686         }
1687     }
1688 
verifySetPropertyFails(int areaId)1689     private void verifySetPropertyFails(int areaId) {
1690         if (!isAtLeastU()) {
1691             // The precondition check in CarPropertyService that throws an IllegalArgumentException
1692             // when a client tries to write a property that is implemented as read_only was added in
1693             // Android U. API behavior prior to Android U is undefined so skipping this check.
1694             return;
1695         }
1696         assertThrows(
1697                 mPropertyName
1698                         + " is a read_only property so setProperty should throw an"
1699                         + " IllegalArgumentException.",
1700                 IllegalArgumentException.class,
1701                 () ->
1702                         mCarPropertyManager.setProperty(
1703                                 mPropertyType,
1704                                 mPropertyId,
1705                                 areaId,
1706                                 getDefaultValue(mPropertyType)));
1707     }
1708 
verifyBooleanPropertySetter()1709     private void verifyBooleanPropertySetter() {
1710         CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig();
1711         for (int areaId : carPropertyConfig.getAreaIds()) {
1712             if (!canWrite(carPropertyConfig, areaId)) {
1713                 verifySetPropertyFails(areaId);
1714                 continue;
1715             }
1716             for (Boolean valueToSet : List.of(Boolean.TRUE, Boolean.FALSE)) {
1717                 verifySetProperty(areaId, (T) valueToSet);
1718             }
1719         }
1720     }
1721 
verifyIntegerPropertySetter()1722     private void verifyIntegerPropertySetter() {
1723         CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig();
1724         for (int areaId : carPropertyConfig.getAreaIds()) {
1725             if (!canWrite(carPropertyConfig, areaId)) {
1726                 verifySetPropertyFails(areaId);
1727                 continue;
1728             }
1729             for (Integer valueToSet : getPossibleIntegerValues(areaId)) {
1730                 verifySetProperty(areaId, (T) valueToSet);
1731             }
1732         }
1733         if (!mAllPossibleEnumValues.isEmpty() && isAtLeastU()) {
1734             for (AreaIdConfig<?> areaIdConfig : carPropertyConfig.getAreaIdConfigs()) {
1735                 if (!canWrite(carPropertyConfig, areaIdConfig.getAreaId())) {
1736                     continue;
1737                 }
1738                 for (T valueToSet : (List<T>) areaIdConfig.getSupportedEnumValues()) {
1739                     if (!mAllPossibleUnwritableValues.isEmpty()
1740                             && mAllPossibleUnwritableValues.contains(valueToSet)) {
1741                         assertThrows(
1742                                 "Trying to set an unwritable value: "
1743                                         + valueToSet
1744                                         + " to property: "
1745                                         + mPropertyId
1746                                         + " should throw an "
1747                                         + "IllegalArgumentException",
1748                                 IllegalArgumentException.class,
1749                                 () ->
1750                                         mCarPropertyManager.setProperty(
1751                                                 carPropertyConfig.getPropertyType(), mPropertyId,
1752                                                 areaIdConfig.getAreaId(), valueToSet));
1753                     }
1754                     if (!mAllPossibleUnavailableValues.isEmpty()
1755                             && mAllPossibleUnavailableValues.contains(valueToSet)) {
1756                         assertThrows(
1757                                 "Trying to set an unavailable value: "
1758                                         + valueToSet
1759                                         + " to property: "
1760                                         + mPropertyId
1761                                         + " should throw an "
1762                                         + "PropertyNotAvailableException",
1763                                 PropertyNotAvailableException.class,
1764                                 () ->
1765                                         mCarPropertyManager.setProperty(
1766                                                 carPropertyConfig.getPropertyType(), mPropertyId,
1767                                                 areaIdConfig.getAreaId(), valueToSet));
1768                     }
1769                 }
1770             }
1771         }
1772     }
1773 
verifyFloatPropertySetter()1774     private void verifyFloatPropertySetter() {
1775         for (int areaId : getCarPropertyConfig().getAreaIds()) {
1776             for (T valueToSet : getPossibleFloatValues(areaId)) {
1777                 verifySetProperty(areaId, valueToSet);
1778             }
1779         }
1780     }
1781 
verifySetProperty(int areaId, T valueToSet)1782     private void verifySetProperty(int areaId, T valueToSet) {
1783         CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig();
1784         if (!canWrite(carPropertyConfig, areaId)) {
1785             return;
1786         }
1787 
1788         verifySetPropertyWithNullValueThrowsException(areaId);
1789 
1790         int access = getAreaIdAccessOrElseGlobalAccess(carPropertyConfig, areaId);
1791         if (canWrite(access) && !canRead(access)) {
1792             Log.w(
1793                     TAG,
1794                     "Property: "
1795                             + mPropertyName
1796                             + " will be altered during the test and it is"
1797                             + " not possible to restore.");
1798             verifySetPropertyOkayOrThrowExpectedExceptions(areaId, valueToSet);
1799             return;
1800         }
1801         try {
1802             CarPropertyValue<T> currentCarPropertyValue =
1803                     mCarPropertyManager.getProperty(mPropertyId, areaId);
1804             verifyCarPropertyValue(
1805                     currentCarPropertyValue, areaId, CAR_PROPERTY_VALUE_SOURCE_GETTER);
1806             if (currentCarPropertyValue.getStatus() != CarPropertyValue.STATUS_AVAILABLE) {
1807                 Log.w(
1808                         TAG,
1809                         "Skipping SET verification for propertyId: "
1810                                 + mPropertyId
1811                                 + " areaId: "
1812                                 + areaId
1813                                 + " valueToSet:"
1814                                 + valueToSet
1815                                 + " because getProperty did not have an AVAILABLE status - "
1816                                 + currentCarPropertyValue);
1817                 return;
1818             }
1819             if (valueEquals(valueToSet, currentCarPropertyValue.getValue())) {
1820                 return;
1821             }
1822         } catch (PropertyNotAvailableAndRetryException e) {
1823             Log.w(
1824                     TAG,
1825                     "Skipping SET verification for propertyId: "
1826                             + mPropertyName
1827                             + " areaId: "
1828                             + areaId
1829                             + " valueToSet:"
1830                             + valueToSet
1831                             + " because getProperty threw PropertyNotAvailableAndRetryException - "
1832                             + e);
1833             return;
1834         } catch (PropertyNotAvailableException e) {
1835             verifyPropertyNotAvailableException(e);
1836             return;
1837         } catch (CarInternalErrorException e) {
1838             verifyInternalErrorException(e);
1839             return;
1840         }
1841         CarPropertyValue<T> updatedCarPropertyValue =
1842                 setPropertyAndWaitForChange(
1843                         mCarPropertyManager,
1844                         mPropertyId,
1845                         carPropertyConfig.getPropertyType(),
1846                         areaId,
1847                         valueToSet);
1848         if (updatedCarPropertyValue != null) {
1849             verifyCarPropertyValue(
1850                     updatedCarPropertyValue, areaId, CAR_PROPERTY_VALUE_SOURCE_CALLBACK);
1851         }
1852     }
1853 
verifySetPropertyWithNullValueThrowsException(int areaId)1854     private void verifySetPropertyWithNullValueThrowsException(int areaId) {
1855         assertThrows(
1856                 NullPointerException.class,
1857                 () ->
1858                         mCarPropertyManager.setProperty(
1859                                 mPropertyType, mPropertyId, areaId, /* val= */ null));
1860     }
1861 
verifyHvacTemperatureValueSuggestionSetter()1862     private void verifyHvacTemperatureValueSuggestionSetter() {
1863         CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig();
1864         CarPropertyConfig<?> hvacTemperatureSetCarPropertyConfig =
1865                 getCarPropertyConfig(VehiclePropertyIds.HVAC_TEMPERATURE_SET);
1866         if (hvacTemperatureSetCarPropertyConfig == null) {
1867             return;
1868         }
1869         List<Integer> hvacTemperatureSetConfigArray =
1870                 hvacTemperatureSetCarPropertyConfig.getConfigArray();
1871         if (hvacTemperatureSetConfigArray.isEmpty()) {
1872             return;
1873         }
1874         float minTempInCelsius = hvacTemperatureSetConfigArray.get(0).floatValue() / 10f;
1875         float minTempInFahrenheit = hvacTemperatureSetConfigArray.get(3).floatValue() / 10f;
1876 
1877         Float[] temperatureRequest =
1878                 new Float[] {
1879                     /* requestedValue= */ minTempInCelsius,
1880                     /* units= */ (float) 0x30, // VehicleUnit#CELSIUS
1881                     /* suggestedValueInCelsius= */ 0f,
1882                     /* suggestedValueInFahrenheit= */ 0f
1883                 };
1884         Float[] expectedTemperatureResponse =
1885                 new Float[] {
1886                     /* requestedValue= */ minTempInCelsius,
1887                     /* units= */ (float) 0x30, // VehicleUnit#CELSIUS
1888                     /* suggestedValueInCelsius= */ minTempInCelsius,
1889                     /* suggestedValueInFahrenheit= */ minTempInFahrenheit
1890                 };
1891         for (int areaId : carPropertyConfig.getAreaIds()) {
1892             CarPropertyValue<Float[]> updatedCarPropertyValue =
1893                     setPropertyAndWaitForChange(
1894                             mCarPropertyManager,
1895                             mPropertyId,
1896                             Float[].class,
1897                             areaId,
1898                             temperatureRequest,
1899                             expectedTemperatureResponse);
1900             if (updatedCarPropertyValue != null) {
1901                 verifyCarPropertyValue(
1902                         updatedCarPropertyValue, areaId, CAR_PROPERTY_VALUE_SOURCE_CALLBACK);
1903             }
1904         }
1905     }
1906 
verifySetPropertyOkayOrThrowExpectedExceptions(int areaId, T valueToSet)1907     private void verifySetPropertyOkayOrThrowExpectedExceptions(int areaId, T valueToSet) {
1908         spaceOutCarPropertyManagerActions();
1909         try {
1910             mCarPropertyManager.setProperty(mPropertyType, mPropertyId, areaId, valueToSet);
1911         } catch (PropertyNotAvailableAndRetryException e) {
1912             // Do nothing
1913         } catch (PropertyNotAvailableException e) {
1914             verifyPropertyNotAvailableException(e);
1915         } catch (CarInternalErrorException e) {
1916             verifyInternalErrorException(e);
1917         } catch (Exception e) {
1918             assertWithMessage(
1919                             "Unexpected exception thrown when trying to setProperty on "
1920                                     + mPropertyName
1921                                     + ": "
1922                                     + e)
1923                     .fail();
1924         }
1925     }
1926 
verifyGetNotAvailable(Collection<Integer> areaIdsNotAvailable)1927     private void verifyGetNotAvailable(Collection<Integer> areaIdsNotAvailable) {
1928         CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig();
1929         for (int areaId : carPropertyConfig.getAreaIds()) {
1930             if (!isAreaIdSupportedInList(areaId, areaIdsNotAvailable)) {
1931                 Log.i(
1932                         TAG,
1933                         "Skipping verifyGetNotAvailable because power could not be turned off for"
1934                                 + " power dependent property - "
1935                                 + mPropertyName
1936                                 + " at areaId - "
1937                                 + areaId);
1938                 continue;
1939             }
1940 
1941             try {
1942                 // getProperty may/may not throw exception when the property is not available.
1943                 CarPropertyValue<T> currentValue =
1944                         mCarPropertyManager.getProperty(mPropertyId, areaId);
1945                 assertWithMessage(
1946                                 "When the power is turned off getProperty should throw"
1947                                         + " PropertyNotAvailableException when trying to get a"
1948                                         + " property with StatusCode.NOT_AVAILABLE or return a"
1949                                         + " CarPropertyValue with status UNAVAILABLE."
1950                                         + " Returned CarPropertyValue: "
1951                                         + currentValue.toString())
1952                         .that(currentValue.getStatus())
1953                         .isEqualTo(CarPropertyValue.STATUS_UNAVAILABLE);
1954             } catch (Exception e) {
1955                 // If the property is read or read-write, then this should throw
1956                 // PropertyNotAvailableException. If the property is write-only, then it will throw
1957                 // IllegalArgumentException.
1958                 assertWithMessage(
1959                                 "Getting property "
1960                                         + mPropertyName
1961                                         + " when it's not available"
1962                                         + " should throw either PropertyNotAvailableException or"
1963                                         + " IllegalArgumentException.")
1964                         .that(e.getClass())
1965                         .isAnyOf(
1966                                 PropertyNotAvailableException.class,
1967                                 IllegalArgumentException.class);
1968             }
1969         }
1970     }
1971 
verifySetNotAvailable(Collection<Integer> areaIdsNotAvailable)1972     private void verifySetNotAvailable(Collection<Integer> areaIdsNotAvailable) {
1973         CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig();
1974         if (!AREA_ID_CONFIG_ACCESS_FLAG
1975                 && !(canRead(carPropertyConfig.getAccess())
1976                         && canWrite(carPropertyConfig.getAccess()))) {
1977             return;
1978         }
1979 
1980         T valueToSet = getDefaultValue(mPropertyType);
1981         if (valueToSet == null) {
1982             assertWithMessage("Testing mixed type property is not supported").fail();
1983         }
1984         for (int areaId : carPropertyConfig.getAreaIds()) {
1985             if (!(canRead(carPropertyConfig, areaId) && canWrite(carPropertyConfig, areaId))) {
1986                 continue;
1987             }
1988 
1989             if (!isAreaIdSupportedInList(areaId, areaIdsNotAvailable)) {
1990                 Log.i(
1991                         TAG,
1992                         "Skipping verifySetNotAvailable because power could not be turned off for"
1993                                 + " power dependent property - "
1994                                 + mPropertyName
1995                                 + " at areaId - "
1996                                 + areaId);
1997                 continue;
1998             }
1999 
2000             spaceOutCarPropertyManagerActions();
2001             SetterCallback setterCallback = new SetterCallback(mPropertyId, areaId, valueToSet);
2002             assertWithMessage(
2003                             "Failed to register no change setter callback for "
2004                                     + VehiclePropertyIds.toString(mPropertyId))
2005                     .that(
2006                             subscribePropertyEvents(
2007                                     mCarPropertyManager,
2008                                     setterCallback,
2009                                     mPropertyId,
2010                                     CarPropertyManager.SENSOR_RATE_FASTEST))
2011                     .isTrue();
2012 
2013             try {
2014                 mCarPropertyManager.setProperty(mPropertyType, mPropertyId, areaId, valueToSet);
2015                 CarPropertyValue<T> updatedValue =
2016                         setterCallback.waitForPropertyEvent(SET_PROPERTY_CALLBACK_TIMEOUT_SEC);
2017                 if (updatedValue != null
2018                         && updatedValue.getStatus() == CarPropertyValue.STATUS_AVAILABLE) {
2019                     // If the callback receives a new event with the value set before the timeout,
2020                     // then this check will fail.
2021                     assertWithMessage(
2022                                     "Received onChangeEvent(s) for "
2023                                             + mPropertyName
2024                                             + " with updated value: "
2025                                             + valueToSet
2026                                             + " before 5s timeout. When the power is turned off,"
2027                                             + " this property must not be available to set.")
2028                             .that(updatedValue.getValue())
2029                             .isNotEqualTo(valueToSet);
2030                 }
2031             } catch (Exception e) {
2032                 // In normal cases, this should throw PropertyNotAvailableException.
2033                 // In rare cases, the value we are setting is the same as the current value,
2034                 // which makes the set operation a no-op. So it is possible that no exception
2035                 // is thrown here.
2036                 // It is also possible that this may throw IllegalArgumentException if the value to
2037                 // set is not valid.
2038                 assertWithMessage(
2039                                 "Setting property "
2040                                         + mPropertyName
2041                                         + " when it's not available"
2042                                         + " should throw either PropertyNotAvailableException or"
2043                                         + " IllegalArgumentException.")
2044                         .that(e.getClass())
2045                         .isAnyOf(
2046                                 PropertyNotAvailableException.class,
2047                                 IllegalArgumentException.class);
2048             } finally {
2049                 unsubscribePropertyEvents(mCarPropertyManager, setterCallback, mPropertyId);
2050             }
2051         }
2052     }
2053 
verifyAdasPropertyErrorState(Collection<Integer> areaIdsDisabled)2054     private void verifyAdasPropertyErrorState(Collection<Integer> areaIdsDisabled) {
2055         CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig();
2056         if (!AREA_ID_CONFIG_ACCESS_FLAG && !canRead(carPropertyConfig.getAccess())) {
2057             return;
2058         }
2059 
2060         for (int areaId : carPropertyConfig.getAreaIds()) {
2061             if (!canRead(carPropertyConfig, areaId)) {
2062                 continue;
2063             }
2064             if (!isAreaIdSupportedInList(areaId, areaIdsDisabled)) {
2065                 Log.i(
2066                         TAG,
2067                         "Skipping verifyAdasPropertyErrorState because could not disable feature"
2068                                 + " for ADAS STATE property - "
2069                                 + mPropertyName
2070                                 + " at areaId - "
2071                                 + areaId);
2072                 continue;
2073             }
2074 
2075             Integer adasState = mCarPropertyManager.getIntProperty(mPropertyId, areaId);
2076             assertWithMessage(
2077                             "When ADAS feature is disabled, "
2078                                     + mPropertyName
2079                                     + " must be set to "
2080                                     + ErrorState.NOT_AVAILABLE_DISABLED
2081                                     + " (ErrorState.NOT_AVAILABLE_DISABLED).")
2082                     .that(adasState)
2083                     .isEqualTo(ErrorState.NOT_AVAILABLE_DISABLED);
2084         }
2085     }
2086 
getUpdatesPerAreaId(int changeMode)2087     private static int getUpdatesPerAreaId(int changeMode) {
2088         return changeMode != CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS ? 1 : 2;
2089     }
2090 
getRegisterCallbackTimeoutMillis(int changeMode, float minSampleRate)2091     private static long getRegisterCallbackTimeoutMillis(int changeMode, float minSampleRate) {
2092         long timeoutMillis = 1500;
2093         if (changeMode == CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS) {
2094             float secondsToMillis = 1_000;
2095             long bufferMillis = 1_000; // 1 second
2096             timeoutMillis =
2097                     ((long)
2098                                     ((1.0f / minSampleRate)
2099                                             * secondsToMillis
2100                                             * getUpdatesPerAreaId(changeMode)))
2101                             + bufferMillis;
2102         }
2103         return timeoutMillis;
2104     }
2105 
subscribePropertyEvents( CarPropertyManager carPropertyManager, CarPropertyManager.CarPropertyEventCallback callback, int propertyId, float updateRateHz)2106     private static boolean subscribePropertyEvents(
2107             CarPropertyManager carPropertyManager,
2108             CarPropertyManager.CarPropertyEventCallback callback,
2109             int propertyId,
2110             float updateRateHz) {
2111         if (isAtLeastV() && Flags.variableUpdateRate()) {
2112             // Use new API if at least V.
2113             return carPropertyManager.subscribePropertyEvents(
2114                     List.of(
2115                             new Subscription.Builder(propertyId)
2116                                     .setUpdateRateHz(updateRateHz)
2117                                     .setVariableUpdateRateEnabled(false)
2118                                     .build()),
2119                     /* callbackExecutor= */ null,
2120                     callback);
2121         } else {
2122             return carPropertyManager.registerCallback(callback, propertyId, updateRateHz);
2123         }
2124     }
2125 
subscribePropertyEvents( CarPropertyManager.CarPropertyEventCallback callback, int propertyId, float updateRateHz)2126     private boolean subscribePropertyEvents(
2127             CarPropertyManager.CarPropertyEventCallback callback,
2128             int propertyId,
2129             float updateRateHz) {
2130         return subscribePropertyEvents(mCarPropertyManager, callback, propertyId, updateRateHz);
2131     }
2132 
unsubscribePropertyEvents( CarPropertyManager carPropertyManager, CarPropertyManager.CarPropertyEventCallback callback, int propertyId)2133     private static void unsubscribePropertyEvents(
2134             CarPropertyManager carPropertyManager,
2135             CarPropertyManager.CarPropertyEventCallback callback,
2136             int propertyId) {
2137         if (isAtLeastV() && Flags.variableUpdateRate()) {
2138             // Use new API if at least V.
2139             carPropertyManager.unsubscribePropertyEvents(propertyId, callback);
2140         } else {
2141             carPropertyManager.unregisterCallback(callback, propertyId);
2142         }
2143     }
2144 
unsubscribePropertyEvents( CarPropertyManager.CarPropertyEventCallback callback, int propertyId)2145     private void unsubscribePropertyEvents(
2146             CarPropertyManager.CarPropertyEventCallback callback, int propertyId) {
2147         unsubscribePropertyEvents(mCarPropertyManager, callback, propertyId);
2148     }
2149 
verifyCarPropertyValueCallback()2150     private void verifyCarPropertyValueCallback() {
2151         CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig();
2152         if (!canReadAllAreaIds(carPropertyConfig)) {
2153             // This means we specify read permission for one property, but the OEM specify it as
2154             // write-only. This will only happen for properties that we allow READ_WRITE or WRITE.
2155             // We currently do not have such system property.
2156             return;
2157         }
2158         int updatesPerAreaId = getUpdatesPerAreaId(mChangeMode);
2159         long timeoutMillis =
2160                 getRegisterCallbackTimeoutMillis(mChangeMode, carPropertyConfig.getMinSampleRate());
2161 
2162         CarPropertyValueCallback carPropertyValueCallback =
2163                 new CarPropertyValueCallback(
2164                         mPropertyName,
2165                         carPropertyConfig.getAreaIds(),
2166                         updatesPerAreaId,
2167                         timeoutMillis);
2168         assertWithMessage("Failed to register callback for " + mPropertyName)
2169                 .that(
2170                         subscribePropertyEvents(
2171                                 carPropertyValueCallback,
2172                                 mPropertyId,
2173                                 carPropertyConfig.getMaxSampleRate()))
2174                 .isTrue();
2175         SparseArray<List<CarPropertyValue<?>>> areaIdToCarPropertyValues =
2176                 carPropertyValueCallback.getAreaIdToCarPropertyValues();
2177         unsubscribePropertyEvents(carPropertyValueCallback, mPropertyId);
2178 
2179         for (int areaId : carPropertyConfig.getAreaIds()) {
2180             List<CarPropertyValue<?>> carPropertyValues = areaIdToCarPropertyValues.get(areaId);
2181             assertWithMessage(mPropertyName + " callback value list is null for area ID: " + areaId)
2182                     .that(carPropertyValues)
2183                     .isNotNull();
2184             assertWithMessage(
2185                             mPropertyName
2186                                     + " callback values did not receive "
2187                                     + updatesPerAreaId
2188                                     + " updates for area ID: "
2189                                     + areaId)
2190                     .that(carPropertyValues.size())
2191                     .isAtLeast(updatesPerAreaId);
2192             for (CarPropertyValue<?> carPropertyValue : carPropertyValues) {
2193                 verifyCarPropertyValue(
2194                         carPropertyValue,
2195                         carPropertyValue.getAreaId(),
2196                         CAR_PROPERTY_VALUE_SOURCE_CALLBACK);
2197             }
2198         }
2199     }
2200 
verifyAccess_isSubsetOfOtherAccess(int subAccess, Set<Integer> superAccess)2201     private void verifyAccess_isSubsetOfOtherAccess(int subAccess, Set<Integer> superAccess) {
2202         assertWithMessage(
2203                         "access mode for property: "
2204                                 + mPropertyName
2205                                 + " must be one of: "
2206                                 + accessSetToString(superAccess))
2207                 .that(subAccess)
2208                 .isIn(superAccess);
2209     }
2210 
verifyCarPropertyConfig()2211     private void verifyCarPropertyConfig() {
2212         CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig();
2213         assertWithMessage(mPropertyName + " CarPropertyConfig must have correct property ID")
2214                 .that(carPropertyConfig.getPropertyId())
2215                 .isEqualTo(mPropertyId);
2216         int carPropConfigAccess = carPropertyConfig.getAccess();
2217         verifyAccess_isSubsetOfOtherAccess(carPropConfigAccess, mAllowedAccessModes);
2218         if (AREA_ID_CONFIG_ACCESS_FLAG) {
2219             for (AreaIdConfig<?> areaIdConfig : carPropertyConfig.getAreaIdConfigs()) {
2220                 int areaAccess = areaIdConfig.getAccess();
2221                 verifyAccess_isSubsetOfOtherAccess(areaAccess, mAllowedAccessModes);
2222                 if (canRead(carPropConfigAccess)) {
2223                     assertWithMessage(
2224                                     "area access mode for property:"
2225                                             + mPropertyName
2226                                             + ", areaId: "
2227                                             + areaIdConfig.getAreaId()
2228                                             + " must be super-set of global access mode, area"
2229                                             + " access mode: "
2230                                             + accessToString(areaAccess)
2231                                             + ", global access: "
2232                                             + accessToString(carPropConfigAccess))
2233                             .that(canRead(areaAccess))
2234                             .isTrue();
2235                 }
2236                 if (canWrite(carPropConfigAccess)) {
2237                     assertWithMessage(
2238                                     "area access mode for property:"
2239                                             + mPropertyName
2240                                             + ", areaId: "
2241                                             + areaIdConfig.getAreaId()
2242                                             + " must be super-set of global access mode, area"
2243                                             + " access mode: "
2244                                             + accessToString(areaAccess)
2245                                             + ", global access: "
2246                                             + accessToString(carPropConfigAccess))
2247                             .that(canWrite(areaAccess))
2248                             .isTrue();
2249                 }
2250             }
2251         }
2252         assertWithMessage(mPropertyName + " must be " + areaTypeToString(mAreaType))
2253                 .that(carPropertyConfig.getAreaType())
2254                 .isEqualTo(mAreaType);
2255         assertWithMessage(mPropertyName + " must be " + changeModeToString(mChangeMode))
2256                 .that(carPropertyConfig.getChangeMode())
2257                 .isEqualTo(mChangeMode);
2258         assertWithMessage(mPropertyName + " must be " + mPropertyType + " type property")
2259                 .that(carPropertyConfig.getPropertyType())
2260                 .isEqualTo(mPropertyType);
2261 
2262         int[] areaIds = carPropertyConfig.getAreaIds();
2263         assertWithMessage(mPropertyName + "'s must have at least 1 area ID defined")
2264                 .that(areaIds.length)
2265                 .isAtLeast(1);
2266         assertWithMessage(
2267                         mPropertyName
2268                                 + "'s area IDs must all be unique: "
2269                                 + Arrays.toString(areaIds))
2270                 .that(
2271                         ImmutableSet.copyOf(
2272                                                 Arrays.stream(areaIds)
2273                                                         .boxed()
2274                                                         .collect(Collectors.toList()))
2275                                         .size()
2276                                 == areaIds.length)
2277                 .isTrue();
2278 
2279         if (mAreaType == VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL) {
2280             assertWithMessage(
2281                             mPropertyName
2282                                     + "'s AreaIds must contain a single 0 since it is "
2283                                     + areaTypeToString(mAreaType))
2284                     .that(areaIds)
2285                     .isEqualTo(new int[] {0});
2286         } else if (mAreaType == VehicleAreaType.VEHICLE_AREA_TYPE_WHEEL) {
2287             verifyValidAreaIdsForAreaType(ALL_POSSIBLE_WHEEL_AREA_IDS);
2288             verifyNoAreaOverlapInAreaIds();
2289         } else if (mAreaType == VehicleAreaType.VEHICLE_AREA_TYPE_WINDOW) {
2290             verifyValidAreaIdsForAreaType(ALL_POSSIBLE_WINDOW_AREA_IDS);
2291             verifyNoAreaOverlapInAreaIds();
2292         } else if (mAreaType == VehicleAreaType.VEHICLE_AREA_TYPE_MIRROR) {
2293             verifyValidAreaIdsForAreaType(ALL_POSSIBLE_MIRROR_AREA_IDS);
2294             verifyNoAreaOverlapInAreaIds();
2295         } else if (mAreaType == VehicleAreaType.VEHICLE_AREA_TYPE_SEAT
2296                 && mPropertyId != VehiclePropertyIds.INFO_DRIVER_SEAT) {
2297             verifyValidAreaIdsForAreaType(ALL_POSSIBLE_SEAT_AREA_IDS);
2298             verifyNoAreaOverlapInAreaIds();
2299         } else if (mAreaType == VehicleAreaType.VEHICLE_AREA_TYPE_DOOR) {
2300             verifyValidAreaIdsForAreaType(ALL_POSSIBLE_DOOR_AREA_IDS);
2301             verifyNoAreaOverlapInAreaIds();
2302         } else if (mAreaType == VehicleAreaType.VEHICLE_AREA_TYPE_VENDOR) {
2303             verifyNoAreaOverlapInAreaIds();
2304         }
2305 
2306         if (mAreaIdsVerifier.isPresent()) {
2307             mAreaIdsVerifier.get().verify(mVerifierContext, areaIds);
2308         }
2309 
2310         if (mChangeMode == CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS) {
2311             verifyContinuousCarPropertyConfig();
2312         } else {
2313             verifyNonContinuousCarPropertyConfig();
2314         }
2315 
2316         mCarPropertyConfigVerifier.ifPresent(
2317                 carPropertyConfigVerifier ->
2318                         carPropertyConfigVerifier.verify(mVerifierContext, carPropertyConfig));
2319 
2320         if (!mPossibleConfigArrayValues.isEmpty()) {
2321             assertWithMessage(mPropertyName + " configArray must specify supported values")
2322                     .that(carPropertyConfig.getConfigArray().size())
2323                     .isGreaterThan(0);
2324             for (Integer supportedValue : carPropertyConfig.getConfigArray()) {
2325                 assertWithMessage(
2326                                 mPropertyName
2327                                         + " configArray value must be a defined "
2328                                         + "value: "
2329                                         + supportedValue)
2330                         .that(supportedValue)
2331                         .isIn(mPossibleConfigArrayValues);
2332             }
2333         }
2334 
2335         mConfigArrayVerifier.ifPresent(
2336                 configArrayVerifier ->
2337                         configArrayVerifier.verify(
2338                                 mVerifierContext, carPropertyConfig.getConfigArray()));
2339 
2340         if (mPossibleConfigArrayValues.isEmpty()
2341                 && !mConfigArrayVerifier.isPresent()
2342                 && !mCarPropertyConfigVerifier.isPresent()) {
2343             assertWithMessage(mPropertyName + " configArray is undefined, so it must be empty")
2344                     .that(carPropertyConfig.getConfigArray().size())
2345                     .isEqualTo(0);
2346         }
2347 
2348         for (int areaId : areaIds) {
2349             T areaIdMinValue = (T) carPropertyConfig.getMinValue(areaId);
2350             T areaIdMaxValue = (T) carPropertyConfig.getMaxValue(areaId);
2351             if (mRequireMinMaxValues) {
2352                 assertWithMessage(
2353                                 mPropertyName
2354                                         + " - area ID: "
2355                                         + areaId
2356                                         + " must have min value defined")
2357                         .that(areaIdMinValue)
2358                         .isNotNull();
2359                 assertWithMessage(
2360                                 mPropertyName
2361                                         + " - area ID: "
2362                                         + areaId
2363                                         + " must have max value defined")
2364                         .that(areaIdMaxValue)
2365                         .isNotNull();
2366 
2367                 if (CAR_PROPERTY_SUPPORTED_VALUE_FLAG) {
2368                     assertWithMessage(
2369                                     mPropertyName
2370                                             + " - area ID: "
2371                                             + areaId
2372                                             + " config must set hasMaxSupportedValue to true")
2373                             .that(carPropertyConfig.getAreaIdConfig(areaId).hasMaxSupportedValue())
2374                             .isTrue();
2375                     assertWithMessage(
2376                                     mPropertyName
2377                                             + " - area ID: "
2378                                             + areaId
2379                                             + " config must set hasMinSupportedValue to true")
2380                             .that(carPropertyConfig.getAreaIdConfig(areaId).hasMinSupportedValue())
2381                             .isTrue();
2382                 }
2383             }
2384 
2385             if (mRequireMinValuesToBeZero) {
2386                 assertWithMessage(
2387                                 mPropertyName + " - area ID: " + areaId + " min value must be zero")
2388                         .that(areaIdMinValue)
2389                         .isEqualTo(0);
2390             }
2391             if (mRequireZeroToBeContainedInMinMaxRanges) {
2392                 assertWithMessage(
2393                                 mPropertyName
2394                                         + " - areaId: "
2395                                         + areaId
2396                                         + "'s max and min range must contain zero")
2397                         .that(verifyMaxAndMinRangeContainsZero(areaIdMinValue, areaIdMaxValue))
2398                         .isTrue();
2399             }
2400             if (areaIdMinValue != null || areaIdMaxValue != null) {
2401                 assertWithMessage(
2402                                 mPropertyName
2403                                         + " - areaId: "
2404                                         + areaId
2405                                         + "'s max value must be >= min value")
2406                         .that(verifyMaxAndMin(areaIdMinValue, areaIdMaxValue))
2407                         .isTrue();
2408             }
2409 
2410             if (mRequirePropertyValueToBeInConfigArray && isAtLeastU()) {
2411                 List<?> supportedEnumValues =
2412                         carPropertyConfig.getAreaIdConfig(areaId).getSupportedEnumValues();
2413                 assertWithMessage(
2414                                 mPropertyName
2415                                         + " - areaId: "
2416                                         + areaId
2417                                         + "'s supported enum values must match the values in the"
2418                                         + " config array.")
2419                         .that(carPropertyConfig.getConfigArray())
2420                         .containsExactlyElementsIn(supportedEnumValues);
2421             }
2422 
2423             if (mChangeMode == CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE
2424                     && !mAllPossibleEnumValues.isEmpty()
2425                     && isAtLeastU()) {
2426                 List<?> supportedEnumValues =
2427                         carPropertyConfig.getAreaIdConfig(areaId).getSupportedEnumValues();
2428                 assertWithMessage(
2429                                 mPropertyName
2430                                         + " - areaId: "
2431                                         + areaId
2432                                         + "'s supported enum values must be defined")
2433                         .that(supportedEnumValues)
2434                         .isNotEmpty();
2435                 assertWithMessage(
2436                                 mPropertyName
2437                                         + " - areaId: "
2438                                         + areaId
2439                                         + "'s supported enum values must not contain any"
2440                                         + " duplicates")
2441                         .that(supportedEnumValues)
2442                         .containsNoDuplicates();
2443                 assertWithMessage(
2444                                 mPropertyName
2445                                         + " - areaId: "
2446                                         + areaId
2447                                         + "'s supported enum values "
2448                                         + supportedEnumValues
2449                                         + " must all exist in all possible enum set "
2450                                         + mAllPossibleEnumValues)
2451                         .that(mAllPossibleEnumValues.containsAll(supportedEnumValues))
2452                         .isTrue();
2453                 if (CAR_PROPERTY_SUPPORTED_VALUE_FLAG) {
2454                     assertWithMessage(
2455                                     mPropertyName
2456                                             + " - areaId: "
2457                                             + areaId
2458                                             + " config must set hasSupportedValuesList to true")
2459                             .that(
2460                                     carPropertyConfig
2461                                             .getAreaIdConfig(areaId)
2462                                             .hasSupportedValuesList())
2463                             .isTrue();
2464                 }
2465             } else if (isAtLeastU()) {
2466                 assertWithMessage(
2467                                 mPropertyName
2468                                         + " - areaId: "
2469                                         + areaId
2470                                         + "'s supported enum values must be empty since property"
2471                                         + " does not support an enum")
2472                         .that(carPropertyConfig.getAreaIdConfig(areaId).getSupportedEnumValues())
2473                         .isEmpty();
2474             }
2475         }
2476     }
2477 
verifyMaxAndMinRangeContainsZero(T min, T max)2478     private boolean verifyMaxAndMinRangeContainsZero(T min, T max) {
2479         int propertyType = mPropertyId & VehiclePropertyType.MASK;
2480         switch (propertyType) {
2481             case VehiclePropertyType.INT32:
2482                 return (Integer) max >= 0 && (Integer) min <= 0;
2483             case VehiclePropertyType.INT64:
2484                 return (Long) max >= 0 && (Long) min <= 0;
2485             case VehiclePropertyType.FLOAT:
2486                 return (Float) max >= 0 && (Float) min <= 0;
2487             default:
2488                 return false;
2489         }
2490     }
2491 
verifyMaxAndMin(T min, T max)2492     private boolean verifyMaxAndMin(T min, T max) {
2493         int propertyType = mPropertyId & VehiclePropertyType.MASK;
2494         switch (propertyType) {
2495             case VehiclePropertyType.INT32:
2496                 return (Integer) max >= (Integer) min;
2497             case VehiclePropertyType.INT64:
2498                 return (Long) max >= (Long) min;
2499             case VehiclePropertyType.FLOAT:
2500                 return (Float) max >= (Float) min;
2501             default:
2502                 return false;
2503         }
2504     }
2505 
verifyContinuousCarPropertyConfig()2506     private void verifyContinuousCarPropertyConfig() {
2507         CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig();
2508         assertWithMessage(
2509                         mPropertyName
2510                                 + " must define max sample rate since change mode is "
2511                                 + changeModeToString(mChangeMode))
2512                 .that(carPropertyConfig.getMaxSampleRate())
2513                 .isGreaterThan(0);
2514         assertWithMessage(
2515                         mPropertyName
2516                                 + " must define min sample rate since change mode is "
2517                                 + changeModeToString(mChangeMode))
2518                 .that(carPropertyConfig.getMinSampleRate())
2519                 .isGreaterThan(0);
2520         assertWithMessage(mPropertyName + " max sample rate must be >= min sample rate")
2521                 .that(carPropertyConfig.getMaxSampleRate() >= carPropertyConfig.getMinSampleRate())
2522                 .isTrue();
2523     }
2524 
verifyNonContinuousCarPropertyConfig()2525     private void verifyNonContinuousCarPropertyConfig() {
2526         CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig();
2527         assertWithMessage(
2528                         mPropertyName
2529                                 + " must define max sample rate as 0 since change mode is "
2530                                 + changeModeToString(mChangeMode))
2531                 .that(carPropertyConfig.getMaxSampleRate())
2532                 .isEqualTo(0);
2533         assertWithMessage(
2534                         mPropertyName
2535                                 + " must define min sample rate as 0 since change mode is "
2536                                 + changeModeToString(mChangeMode))
2537                 .that(carPropertyConfig.getMinSampleRate())
2538                 .isEqualTo(0);
2539     }
2540 
handleGetPropertyExceptions(Exception e)2541     private void handleGetPropertyExceptions(Exception e) {
2542         if (e instanceof PropertyNotAvailableException) {
2543             verifyPropertyNotAvailableException((PropertyNotAvailableException) e);
2544         } else if (e instanceof CarInternalErrorException) {
2545             verifyInternalErrorException((CarInternalErrorException) e);
2546         }
2547         sExceptionClassOnGet = e.getClass();
2548     }
2549 
handleClassSpecificGetPropertyExceptions( Exception e, Class<?> expectedClass, int areaId)2550     private void handleClassSpecificGetPropertyExceptions(
2551             Exception e, Class<?> expectedClass, int areaId) {
2552         if (e instanceof IllegalArgumentException) {
2553             if (mPropertyType.equals(expectedClass)) {
2554                 assertWithMessage(
2555                                 "getProperty for "
2556                                         + expectedClass
2557                                         + " class should not throw"
2558                                         + " IllegalArgumentException for valid propertyId: "
2559                                         + mPropertyId
2560                                         + " areaId: "
2561                                         + areaId
2562                                         + " of  type: "
2563                                         + mPropertyType
2564                                         + " if property is"
2565                                         + " readable")
2566                         .fail();
2567             }
2568         } else {
2569             handleGetPropertyExceptions(e);
2570         }
2571     }
2572 
verifyClassSpecificGetPropertyResults( T value, Class<?> expectedClass, int areaId)2573     private void verifyClassSpecificGetPropertyResults(
2574             T value, Class<?> expectedClass, int areaId) {
2575         if (!mPropertyType.equals(expectedClass)) {
2576             assertWithMessage(
2577                             "getProperty for "
2578                                     + expectedClass
2579                                     + " class should throw"
2580                                     + " IllegalArgumentException for valid propertyId: "
2581                                     + mPropertyId
2582                                     + " areaId: "
2583                                     + areaId
2584                                     + " of"
2585                                     + " type: "
2586                                     + mPropertyType
2587                                     + " if property is readable")
2588                     .fail();
2589         }
2590         verifyCarPropertyValue(
2591                 mPropertyId,
2592                 areaId,
2593                 CarPropertyValue.STATUS_AVAILABLE,
2594                 SystemClock.elapsedRealtimeNanos(),
2595                 value,
2596                 areaId,
2597                 CAR_PROPERTY_VALUE_SOURCE_GETTER);
2598     }
2599 
verifyCarPropertyValueGetter()2600     private void verifyCarPropertyValueGetter() {
2601         CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig();
2602         if (!AREA_ID_CONFIG_ACCESS_FLAG && !canRead(carPropertyConfig.getAccess())) {
2603             verifyGetPropertyFails(carPropertyConfig.getAreaIds()[0]);
2604             return;
2605         }
2606         for (int areaId : carPropertyConfig.getAreaIds()) {
2607             if (!canRead(carPropertyConfig, areaId)) {
2608                 verifyGetPropertyFails(areaId);
2609                 continue;
2610             }
2611 
2612             CarPropertyValue<?> carPropertyValue = null;
2613             try {
2614                 carPropertyValue = mCarPropertyManager.getProperty(mPropertyId, areaId);
2615                 verifyCarPropertyValue(carPropertyValue, areaId, CAR_PROPERTY_VALUE_SOURCE_GETTER);
2616             } catch (PropertyNotAvailableException | CarInternalErrorException e) {
2617                 handleGetPropertyExceptions(e);
2618             }
2619 
2620             try {
2621                 Boolean value = mCarPropertyManager.getBooleanProperty(mPropertyId, areaId);
2622                 verifyClassSpecificGetPropertyResults((T) value, Boolean.class, areaId);
2623             } catch (IllegalArgumentException
2624                     | PropertyNotAvailableException
2625                     | CarInternalErrorException e) {
2626                 handleClassSpecificGetPropertyExceptions(e, Boolean.class, areaId);
2627             }
2628             try {
2629                 Integer value = mCarPropertyManager.getIntProperty(mPropertyId, areaId);
2630                 verifyClassSpecificGetPropertyResults((T) value, Integer.class, areaId);
2631             } catch (IllegalArgumentException
2632                     | PropertyNotAvailableException
2633                     | CarInternalErrorException e) {
2634                 handleClassSpecificGetPropertyExceptions(e, Integer.class, areaId);
2635             }
2636             try {
2637                 Float value = mCarPropertyManager.getFloatProperty(mPropertyId, areaId);
2638                 verifyClassSpecificGetPropertyResults((T) value, Float.class, areaId);
2639             } catch (IllegalArgumentException
2640                     | PropertyNotAvailableException
2641                     | CarInternalErrorException e) {
2642                 handleClassSpecificGetPropertyExceptions(e, Float.class, areaId);
2643             }
2644             try {
2645                 int[] primitiveArray = mCarPropertyManager.getIntArrayProperty(mPropertyId, areaId);
2646                 Integer[] value = new Integer[primitiveArray.length];
2647                 for (int i = 0; i < primitiveArray.length; i++) {
2648                     value[i] = (Integer) primitiveArray[i];
2649                 }
2650                 verifyClassSpecificGetPropertyResults((T) value, Integer[].class, areaId);
2651             } catch (IllegalArgumentException
2652                     | PropertyNotAvailableException
2653                     | CarInternalErrorException e) {
2654                 handleClassSpecificGetPropertyExceptions(e, Integer[].class, areaId);
2655             }
2656         }
2657     }
2658 
verifyGetPropertyFails(int areaId)2659     private void verifyGetPropertyFails(int areaId) {
2660         assertGetPropertyThrowsException(
2661                 mPropertyName
2662                         + " is a write_only property so getProperty should throw an"
2663                         + " IllegalArgumentException.",
2664                 IllegalArgumentException.class,
2665                 mPropertyId,
2666                 areaId);
2667     }
2668 
verifyPropertyNotAvailableException(PropertyNotAvailableException e)2669     private static void verifyPropertyNotAvailableException(PropertyNotAvailableException e) {
2670         if (!isAtLeastU()) {
2671             return;
2672         }
2673         assertThat(((PropertyNotAvailableException) e).getDetailedErrorCode())
2674                 .isIn(PROPERTY_NOT_AVAILABLE_ERROR_CODES);
2675         int vendorErrorCode = e.getVendorErrorCode();
2676         assertThat(vendorErrorCode).isAtLeast(VENDOR_ERROR_CODE_MINIMUM_VALUE);
2677         assertThat(vendorErrorCode).isAtMost(VENDOR_ERROR_CODE_MAXIMUM_VALUE);
2678     }
2679 
verifyInternalErrorException(CarInternalErrorException e)2680     private static void verifyInternalErrorException(CarInternalErrorException e) {
2681         if (!isAtLeastU()) {
2682             return;
2683         }
2684         int vendorErrorCode = e.getVendorErrorCode();
2685         assertThat(vendorErrorCode).isAtLeast(VENDOR_ERROR_CODE_MINIMUM_VALUE);
2686         assertThat(vendorErrorCode).isAtMost(VENDOR_ERROR_CODE_MAXIMUM_VALUE);
2687     }
2688 
verifyCarPropertyValue( CarPropertyValue<?> carPropertyValue, int expectedAreaId, String source)2689     private void verifyCarPropertyValue(
2690             CarPropertyValue<?> carPropertyValue, int expectedAreaId, String source) {
2691         verifyCarPropertyValue(
2692                 carPropertyValue.getPropertyId(),
2693                 carPropertyValue.getAreaId(),
2694                 carPropertyValue.getStatus(),
2695                 carPropertyValue.getTimestamp(),
2696                 (T) carPropertyValue.getValue(),
2697                 expectedAreaId,
2698                 source);
2699     }
2700 
verifyCarPropertyValue( int propertyId, int areaId, int status, long timestampNanos, T value, int expectedAreaId, String source)2701     private void verifyCarPropertyValue(
2702             int propertyId,
2703             int areaId,
2704             int status,
2705             long timestampNanos,
2706             T value,
2707             int expectedAreaId,
2708             String source) {
2709         CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig();
2710         assertWithMessage(
2711                         mPropertyName
2712                                 + " - areaId: "
2713                                 + areaId
2714                                 + " - source: "
2715                                 + source
2716                                 + " value must have correct property ID")
2717                 .that(propertyId)
2718                 .isEqualTo(mPropertyId);
2719         assertWithMessage(
2720                         mPropertyName
2721                                 + " - areaId: "
2722                                 + areaId
2723                                 + " - source: "
2724                                 + source
2725                                 + " value must have correct area id: "
2726                                 + areaId)
2727                 .that(areaId)
2728                 .isEqualTo(expectedAreaId);
2729         assertWithMessage(
2730                         mPropertyName
2731                                 + " - areaId: "
2732                                 + areaId
2733                                 + " - source: "
2734                                 + source
2735                                 + " area ID must be in carPropertyConfig#getAreaIds()")
2736                 .that(
2737                         Arrays.stream(carPropertyConfig.getAreaIds())
2738                                 .boxed()
2739                                 .collect(Collectors.toList())
2740                                 .contains(areaId))
2741                 .isTrue();
2742         assertWithMessage(
2743                         mPropertyName
2744                                 + " - areaId: "
2745                                 + areaId
2746                                 + " - source: "
2747                                 + source
2748                                 + " value must have a valid status: "
2749                                 + VALID_CAR_PROPERTY_VALUE_STATUSES)
2750                 .that(VALID_CAR_PROPERTY_VALUE_STATUSES)
2751                 .contains(status);
2752         assertWithMessage(
2753                         mPropertyName
2754                                 + " - areaId: "
2755                                 + areaId
2756                                 + " - source: "
2757                                 + source
2758                                 + " timestamp must use the SystemClock.elapsedRealtimeNanos() time"
2759                                 + " base")
2760                 .that(timestampNanos)
2761                 .isAtLeast(0);
2762         assertWithMessage(
2763                         mPropertyName
2764                                 + " - areaId: "
2765                                 + areaId
2766                                 + " - source: "
2767                                 + source
2768                                 + " timestamp must use the SystemClock.elapsedRealtimeNanos() time"
2769                                 + " base")
2770                 .that(timestampNanos)
2771                 .isLessThan(SystemClock.elapsedRealtimeNanos());
2772         assertWithMessage(
2773                         mPropertyName
2774                                 + " - areaId: "
2775                                 + areaId
2776                                 + " - source: "
2777                                 + source
2778                                 + " must return "
2779                                 + mPropertyType
2780                                 + " type value")
2781                 .that(value.getClass())
2782                 .isEqualTo(mPropertyType);
2783 
2784         // Only validate value if STATUS_AVAILABLE
2785         if (status != CarPropertyValue.STATUS_AVAILABLE) {
2786             return;
2787         }
2788 
2789         mCarPropertyValueVerifier.ifPresent(
2790                 propertyValueVerifier ->
2791                         propertyValueVerifier.verify(
2792                                 mVerifierContext,
2793                                 carPropertyConfig,
2794                                 propertyId,
2795                                 areaId,
2796                                 timestampNanos,
2797                                 value));
2798 
2799         if (mRequirePropertyValueToBeInConfigArray) {
2800             assertWithMessage(
2801                             mPropertyName
2802                                     + " - areaId: "
2803                                     + areaId
2804                                     + " - source: "
2805                                     + source
2806                                     + " value must be listed in configArray,"
2807                                     + " configArray:")
2808                     .that(carPropertyConfig.getConfigArray())
2809                     .contains(value);
2810         }
2811 
2812         if (isAtLeastU()) {
2813             List<T> supportedEnumValues =
2814                     carPropertyConfig.getAreaIdConfig(areaId).getSupportedEnumValues();
2815             if (!supportedEnumValues.isEmpty()) {
2816                 if (mEnumIsBitMap) {
2817                     int allValidValues = 0;
2818                     for (T bitEnumValue : supportedEnumValues) {
2819                         allValidValues |= ((Integer) bitEnumValue).intValue();
2820                     }
2821                     assertWithMessage(
2822                                     mPropertyName
2823                                             + " - areaId: "
2824                                             + areaId
2825                                             + " - source: "
2826                                             + source
2827                                             + " value must be a combination of values listed in "
2828                                             + "getSupportedEnumValues()")
2829                             .that(((Integer) value).intValue() & allValidValues)
2830                             .isEqualTo(value);
2831                 } else {
2832                     assertWithMessage(
2833                                     mPropertyName
2834                                             + " - areaId: "
2835                                             + areaId
2836                                             + " - source: "
2837                                             + source
2838                                             + " value must be listed in getSupportedEnumValues()")
2839                             .that(value)
2840                             .isIn(supportedEnumValues);
2841                 }
2842             }
2843         }
2844 
2845         T areaIdMinValue = (T) carPropertyConfig.getMinValue(areaId);
2846         T areaIdMaxValue = (T) carPropertyConfig.getMaxValue(areaId);
2847         if (areaIdMinValue != null && areaIdMaxValue != null) {
2848             assertWithMessage(
2849                             "Property value: "
2850                                     + value
2851                                     + " must be between the max: "
2852                                     + areaIdMaxValue
2853                                     + " and min: "
2854                                     + areaIdMinValue
2855                                     + " values for area ID: "
2856                                     + Integer.toHexString(areaId))
2857                     .that(verifyValueInRange(areaIdMinValue, areaIdMaxValue, (T) value))
2858                     .isTrue();
2859         }
2860 
2861         if (mVerifyErrorStates) {
2862             assertWithMessage(
2863                             "When ADAS feature is enabled, "
2864                                     + VehiclePropertyIds.toString(mPropertyId)
2865                                     + " must not be set to "
2866                                     + ErrorState.NOT_AVAILABLE_DISABLED
2867                                     + " (ErrorState#NOT_AVAILABLE_DISABLED")
2868                     .that((Integer) value)
2869                     .isNotEqualTo(ErrorState.NOT_AVAILABLE_DISABLED);
2870         }
2871     }
2872 
verifyValueInRange(T min, T max, T value)2873     private boolean verifyValueInRange(T min, T max, T value) {
2874         int propertyType = mPropertyId & VehiclePropertyType.MASK;
2875         switch (propertyType) {
2876             case VehiclePropertyType.INT32:
2877                 return ((Integer) value >= (Integer) min && (Integer) value <= (Integer) max);
2878             case VehiclePropertyType.INT64:
2879                 return ((Long) value >= (Long) min && (Long) value <= (Long) max);
2880             case VehiclePropertyType.FLOAT:
2881                 return (((Float) value > (Float) min || valueEquals(value, min))
2882                         && ((Float) value < (Float) max || valueEquals(value, max)));
2883             default:
2884                 return false;
2885         }
2886     }
2887 
generateAllPossibleAreaIds(ImmutableSet<Integer> areas)2888     private static ImmutableSet<Integer> generateAllPossibleAreaIds(ImmutableSet<Integer> areas) {
2889         ImmutableSet.Builder<Integer> allPossibleAreaIdsBuilder = ImmutableSet.builder();
2890         for (int i = 1; i <= areas.size(); i++) {
2891             allPossibleAreaIdsBuilder.addAll(
2892                     Sets.combinations(areas, i).stream()
2893                             .map(
2894                                     areaCombo -> {
2895                                         Integer possibleAreaId = 0;
2896                                         for (Integer area : areaCombo) {
2897                                             possibleAreaId |= area;
2898                                         }
2899                                         return possibleAreaId;
2900                                     })
2901                             .collect(Collectors.toList()));
2902         }
2903         return allPossibleAreaIdsBuilder.build();
2904     }
2905 
verifyValidAreaIdsForAreaType(ImmutableSet<Integer> allPossibleAreaIds)2906     private void verifyValidAreaIdsForAreaType(ImmutableSet<Integer> allPossibleAreaIds) {
2907         for (int areaId : getCarPropertyConfig().getAreaIds()) {
2908             assertWithMessage(
2909                             mPropertyName
2910                                     + "'s area ID must be a valid "
2911                                     + areaTypeToString(mAreaType)
2912                                     + " area ID")
2913                     .that(areaId)
2914                     .isIn(allPossibleAreaIds);
2915         }
2916     }
2917 
verifyNoAreaOverlapInAreaIds()2918     private void verifyNoAreaOverlapInAreaIds() {
2919         CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig();
2920         if (carPropertyConfig.getAreaIds().length < 2) {
2921             return;
2922         }
2923         ImmutableSet<Integer> areaIds =
2924                 ImmutableSet.copyOf(
2925                         Arrays.stream(carPropertyConfig.getAreaIds())
2926                                 .boxed()
2927                                 .collect(Collectors.toList()));
2928         List<Integer> areaIdOverlapCheckResults =
2929                 Sets.combinations(areaIds, 2).stream()
2930                         .map(
2931                                 areaIdPair -> {
2932                                     List<Integer> areaIdPairAsList =
2933                                             areaIdPair.stream().collect(Collectors.toList());
2934                                     return areaIdPairAsList.get(0) & areaIdPairAsList.get(1);
2935                                 })
2936                         .collect(Collectors.toList());
2937 
2938         assertWithMessage(
2939                         mPropertyName
2940                                 + " area IDs: "
2941                                 + Arrays.toString(carPropertyConfig.getAreaIds())
2942                                 + " must contain each area only once (e.g. no bitwise AND overlap)"
2943                                 + " for the area type: "
2944                                 + areaTypeToString(mAreaType))
2945                 .that(
2946                         Collections.frequency(areaIdOverlapCheckResults, 0)
2947                                 == areaIdOverlapCheckResults.size())
2948                 .isTrue();
2949     }
2950 
2951     /** Returns whether all AreaIds for the property is readable. */
canReadAllAreaIds(CarPropertyConfig<T> carPropertyConfig)2952     private boolean canReadAllAreaIds(CarPropertyConfig<T> carPropertyConfig) {
2953         if (!AREA_ID_CONFIG_ACCESS_FLAG) {
2954             return canRead(carPropertyConfig.getAccess());
2955         }
2956         for (int areaId : carPropertyConfig.getAreaIds()) {
2957             if (!canRead(carPropertyConfig, areaId)) {
2958                 return false;
2959             }
2960         }
2961         return true;
2962     }
2963 
2964     /** Verifies that exceptions are thrown when caller does not have read/write permission. */
verifyPermissionNotGrantedException()2965     private void verifyPermissionNotGrantedException() {
2966         // If the client itself already has read/write permissions without adopting any permissions
2967         // from the shell, skip the test.
2968         if (hasReadPermissions(mReadPermissions) || hasWritePermissions(mWritePermissions)) {
2969             return;
2970         }
2971 
2972         CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig();
2973         assertWithMessage(
2974                         mPropertyName
2975                                 + " - property ID: "
2976                                 + mPropertyId
2977                                 + " CarPropertyConfig should not be accessible without"
2978                                 + " permissions.")
2979                 .that(getCarPropertyConfig(/* useCache= */ false))
2980                 .isNull();
2981 
2982         int access = carPropertyConfig.getAccess();
2983         for (int areaId : carPropertyConfig.getAreaIds()) {
2984             if (AREA_ID_CONFIG_ACCESS_FLAG) {
2985                 access = carPropertyConfig.getAreaIdConfig(areaId).getAccess();
2986             }
2987 
2988             if (canRead(access)) {
2989                 assertGetPropertyThrowsException(
2990                         mPropertyName
2991                                 + " - property ID: "
2992                                 + mPropertyId
2993                                 + " - area ID: "
2994                                 + areaId
2995                                 + " should not be able to be read without permissions.",
2996                         SecurityException.class,
2997                         mPropertyId,
2998                         areaId);
2999                 assertThrows(
3000                         SecurityException.class,
3001                         () -> {
3002                             mCarPropertyManager.isPropertyAvailable(mPropertyId, areaId);
3003                         });
3004             }
3005             if (canWrite(access)) {
3006                 assertThrows(
3007                         mPropertyName
3008                                 + " - property ID: "
3009                                 + mPropertyId
3010                                 + " - area ID: "
3011                                 + areaId
3012                                 + " should not be able to be written to without permissions.",
3013                         SecurityException.class,
3014                         () ->
3015                                 mCarPropertyManager.setProperty(
3016                                         mPropertyType,
3017                                         mPropertyId,
3018                                         areaId,
3019                                         getDefaultValue(mPropertyType)));
3020             }
3021         }
3022 
3023         if (!canReadAllAreaIds(carPropertyConfig)) {
3024             return;
3025         }
3026 
3027         // We expect a return value of false and not a SecurityException thrown.
3028         // This is because registerCallback first tries to get the CarPropertyConfig for the
3029         // property, but since no permissions have been granted it can't find the CarPropertyConfig,
3030         // so it immediately returns false.
3031         assertWithMessage(
3032                         mPropertyName
3033                                 + " - property ID: "
3034                                 + mPropertyId
3035                                 + " should not be able to be listened to without permissions.")
3036                 .that(mCarPropertyManager.registerCallback(FAKE_CALLBACK, mPropertyId, 0f))
3037                 .isFalse();
3038 
3039         if (isAtLeastV() && Flags.variableUpdateRate()) {
3040             // For the new API, if the caller does not have read and write permission, it throws
3041             // SecurityException.
3042             assertThrows(
3043                     mPropertyName
3044                             + " - property ID: "
3045                             + mPropertyId
3046                             + " should not be able to be listened to without permissions.",
3047                     SecurityException.class,
3048                     () -> mCarPropertyManager.subscribePropertyEvents(mPropertyId, FAKE_CALLBACK));
3049         }
3050     }
3051 
isAreaIdSupportedInList(int areaIdToFind, Collection<Integer> areaIds)3052     private static boolean isAreaIdSupportedInList(int areaIdToFind, Collection<Integer> areaIds) {
3053         for (Integer areaId : areaIds) {
3054             if ((areaIdToFind & areaId) == areaIdToFind) {
3055                 return true;
3056             }
3057         }
3058         return false;
3059     }
3060 
getAreaIdAccess( CarPropertyConfig<?> carPropertyConfig, int areaId)3061     private static Optional<Integer> getAreaIdAccess(
3062             CarPropertyConfig<?> carPropertyConfig, int areaId) {
3063         return AREA_ID_CONFIG_ACCESS_FLAG
3064                 ? Optional.of(carPropertyConfig.getAreaIdConfig(areaId).getAccess())
3065                 : Optional.empty();
3066     }
3067 
getAreaIdAccessOrElseGlobalAccess( CarPropertyConfig<?> carPropertyConfig, int areaId)3068     private static Integer getAreaIdAccessOrElseGlobalAccess(
3069             CarPropertyConfig<?> carPropertyConfig, int areaId) {
3070         return getAreaIdAccess(carPropertyConfig, areaId).orElse(carPropertyConfig.getAccess());
3071     }
3072 
canRead(int accessMode)3073     private static boolean canRead(int accessMode) {
3074         return accessMode == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ
3075                 || accessMode == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE;
3076     }
3077 
canWrite(int accessMode)3078     private static boolean canWrite(int accessMode) {
3079         return accessMode == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE
3080                 || accessMode == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE;
3081     }
3082 
canRead(CarPropertyConfig<?> carPropertyConfig, int areaId)3083     private static boolean canRead(CarPropertyConfig<?> carPropertyConfig, int areaId) {
3084         return doesAreaIdAccessMatch(
3085                         carPropertyConfig, areaId, CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ)
3086                 || doesAreaIdAccessMatch(
3087                         carPropertyConfig,
3088                         areaId,
3089                         CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE);
3090     }
3091 
canWrite(CarPropertyConfig<?> carPropertyConfig, int areaId)3092     private static boolean canWrite(CarPropertyConfig<?> carPropertyConfig, int areaId) {
3093         return doesAreaIdAccessMatch(
3094                         carPropertyConfig, areaId, CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE)
3095                 || doesAreaIdAccessMatch(
3096                         carPropertyConfig,
3097                         areaId,
3098                         CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE);
3099     }
3100 
doesAreaIdAccessMatch( CarPropertyConfig<?> carPropertyConfig, int areaId, int expectedAccess)3101     private static boolean doesAreaIdAccessMatch(
3102             CarPropertyConfig<?> carPropertyConfig, int areaId, int expectedAccess) {
3103         return getAreaIdAccess(carPropertyConfig, areaId)
3104                 .filter(areaIdAccess -> areaIdAccess == expectedAccess)
3105                 .isPresent();
3106     }
3107 
3108     /** Verifies that hvac temperature is valid. */
verifyHvacTemperatureIsValid( float temp, int minTempTimesTen, int maxTempTimesTen, int incrementTimesTen)3109     public static void verifyHvacTemperatureIsValid(
3110             float temp, int minTempTimesTen, int maxTempTimesTen, int incrementTimesTen) {
3111         int intTempTimesTen = (int) (temp * 10f);
3112         assertWithMessage(
3113                         "The temperature value "
3114                                 + intTempTimesTen
3115                                 + " must be at least "
3116                                 + minTempTimesTen
3117                                 + " and at most "
3118                                 + maxTempTimesTen)
3119                 .that(intTempTimesTen >= minTempTimesTen && intTempTimesTen <= maxTempTimesTen)
3120                 .isTrue();
3121 
3122         int remainder = (intTempTimesTen - minTempTimesTen) % incrementTimesTen;
3123         assertWithMessage(
3124                         "The temperature value "
3125                                 + intTempTimesTen
3126                                 + " is not a valid temperature value. Valid values start from "
3127                                 + minTempTimesTen
3128                                 + " and increment by "
3129                                 + incrementTimesTen
3130                                 + " until the max temperature setting of "
3131                                 + maxTempTimesTen)
3132                 .that(remainder)
3133                 .isEqualTo(0);
3134     }
3135 
3136     /**
3137      * A structure containing verifier context.
3138      *
3139      * <p>This contains VehiclePropertyVerifier members that might be useful for the verification.
3140      */
3141     public static class VerifierContext {
3142         private final CarPropertyManager mCarPropertyManager;
3143 
VerifierContext(CarPropertyManager carPropertyManager)3144         public VerifierContext(CarPropertyManager carPropertyManager) {
3145             mCarPropertyManager = carPropertyManager;
3146         }
3147 
getCarPropertyManager()3148         public CarPropertyManager getCarPropertyManager() {
3149             return mCarPropertyManager;
3150         }
3151     }
3152 
3153     /** An interface for verifying the config array. */
3154     public interface ConfigArrayVerifier {
3155         /** Verifies the config array. Throws exception if not valid. */
verify(VerifierContext verifierContext, List<Integer> configArray)3156         void verify(VerifierContext verifierContext, List<Integer> configArray);
3157     }
3158 
3159     /** An interface for verifying the property value. */
3160     public interface CarPropertyValueVerifier<T> {
3161         /** Verifies the property value. Throws exception if not valid. */
verify( VerifierContext verifierContext, CarPropertyConfig<T> carPropertyConfig, int propertyId, int areaId, long timestampNanos, T value)3162         void verify(
3163                 VerifierContext verifierContext,
3164                 CarPropertyConfig<T> carPropertyConfig,
3165                 int propertyId,
3166                 int areaId,
3167                 long timestampNanos,
3168                 T value);
3169     }
3170 
3171     /** An interface for verifying the areaIds. */
3172     public interface AreaIdsVerifier {
3173         /** Verifies the areaIds. Throws exception if not valid. */
verify(VerifierContext verifierContext, int[] areaIds)3174         void verify(VerifierContext verifierContext, int[] areaIds);
3175     }
3176 
3177     /** An interface for verifying the {@link CarPropertyConfig}. */
3178     public interface CarPropertyConfigVerifier {
3179         /** Verifies the property config. Throws exception if not valid. */
verify(VerifierContext verifierContext, CarPropertyConfig<?> carPropertyConfig)3180         void verify(VerifierContext verifierContext, CarPropertyConfig<?> carPropertyConfig);
3181     }
3182 
3183     /** The builder class. */
3184     public static class Builder<T> {
3185         private final int mPropertyId;
3186         private final ImmutableSet<Integer> mAllowedAccessModes;
3187         private final int mAreaType;
3188         private final int mChangeMode;
3189         private final Class<T> mPropertyType;
3190         private CarPropertyManager mCarPropertyManager;
3191         private boolean mRequiredProperty = false;
3192         private Optional<ConfigArrayVerifier> mConfigArrayVerifier = Optional.empty();
3193         private Optional<CarPropertyValueVerifier<T>> mCarPropertyValueVerifier = Optional.empty();
3194         private Optional<AreaIdsVerifier> mAreaIdsVerifier = Optional.empty();
3195         private Optional<CarPropertyConfigVerifier> mCarPropertyConfigVerifier = Optional.empty();
3196         private Optional<Integer> mDependentOnPropertyId = Optional.empty();
3197         private ImmutableSet<String> mDependentOnPropertyPermissions = ImmutableSet.of();
3198         private ImmutableSet<Integer> mPossibleConfigArrayValues = ImmutableSet.of();
3199         private boolean mEnumIsBitMap = false;
3200         private ImmutableSet<T> mAllPossibleEnumValues = ImmutableSet.of();
3201         private ImmutableSet<T> mAllPossibleUnwritableValues = ImmutableSet.of();
3202         private ImmutableSet<T> mAllPossibleUnavailableValues = ImmutableSet.of();
3203         private boolean mRequirePropertyValueToBeInConfigArray = false;
3204         private boolean mVerifySetterWithConfigArrayValues = false;
3205         private boolean mRequireMinMaxValues = false;
3206         private boolean mRequireMinValuesToBeZero = false;
3207         private boolean mRequireZeroToBeContainedInMinMaxRanges = false;
3208         private boolean mPossiblyDependentOnHvacPowerOn = false;
3209         private boolean mVerifyErrorStates = false;
3210         private int mPowerPropagationDelayMs = 2000;
3211         private final ImmutableSet.Builder<String> mReadPermissionsBuilder = ImmutableSet.builder();
3212         private final ImmutableList.Builder<ImmutableSet<String>> mWritePermissionsBuilder =
3213                 ImmutableList.builder();
3214 
Builder( int propertyId, ImmutableSet<Integer> allowedAccessModes, int areaType, int changeMode, Class<T> propertyType)3215         private Builder(
3216                 int propertyId,
3217                 ImmutableSet<Integer> allowedAccessModes,
3218                 int areaType,
3219                 int changeMode,
3220                 Class<T> propertyType) {
3221             mPropertyId = propertyId;
3222             mAllowedAccessModes = allowedAccessModes;
3223             mAreaType = areaType;
3224             mChangeMode = changeMode;
3225             mPropertyType = propertyType;
3226         }
3227 
getPropertyId()3228         public int getPropertyId() {
3229             return mPropertyId;
3230         }
3231 
isRequired()3232         public boolean isRequired() {
3233             return mRequiredProperty;
3234         }
3235 
3236         /** Sets the car property manager. */
setCarPropertyManager(CarPropertyManager carPropertyManager)3237         public Builder<T> setCarPropertyManager(CarPropertyManager carPropertyManager) {
3238             mCarPropertyManager = carPropertyManager;
3239             return this;
3240         }
3241 
3242         /** Sets the property as required. Test will fail if the property is not supported. */
requireProperty()3243         public Builder<T> requireProperty() {
3244             mRequiredProperty = true;
3245             return this;
3246         }
3247 
3248         /**
3249          * Sets the property as required if condition is true. If condition is true, test will fail
3250          * if the property is not supported.
3251          */
requireProperty(boolean condition)3252         public Builder<T> requireProperty(boolean condition) {
3253             mRequiredProperty = condition;
3254             return this;
3255         }
3256 
3257         /** Sets the config array verifier. */
setConfigArrayVerifier(ConfigArrayVerifier configArrayVerifier)3258         public Builder<T> setConfigArrayVerifier(ConfigArrayVerifier configArrayVerifier) {
3259             mConfigArrayVerifier = Optional.of(configArrayVerifier);
3260             return this;
3261         }
3262 
3263         /** Sets the car property value verifier. */
setCarPropertyValueVerifier( CarPropertyValueVerifier<T> carPropertyValueVerifier)3264         public Builder<T> setCarPropertyValueVerifier(
3265                 CarPropertyValueVerifier<T> carPropertyValueVerifier) {
3266             mCarPropertyValueVerifier = Optional.of(carPropertyValueVerifier);
3267             return this;
3268         }
3269 
3270         /** Sets the areaIds verifier. */
setAreaIdsVerifier(AreaIdsVerifier areaIdsVerifier)3271         public Builder<T> setAreaIdsVerifier(AreaIdsVerifier areaIdsVerifier) {
3272             mAreaIdsVerifier = Optional.of(areaIdsVerifier);
3273             return this;
3274         }
3275 
3276         /** Sets the car property config verifier. */
setCarPropertyConfigVerifier( CarPropertyConfigVerifier carPropertyConfigVerifier)3277         public Builder<T> setCarPropertyConfigVerifier(
3278                 CarPropertyConfigVerifier carPropertyConfigVerifier) {
3279             mCarPropertyConfigVerifier = Optional.of(carPropertyConfigVerifier);
3280             return this;
3281         }
3282 
3283         /** Sets that the property is depending on other properties. */
setDependentOnProperty( Integer dependentPropertyId, ImmutableSet<String> dependentPropertyPermissions)3284         public Builder<T> setDependentOnProperty(
3285                 Integer dependentPropertyId, ImmutableSet<String> dependentPropertyPermissions) {
3286             mDependentOnPropertyId = Optional.of(dependentPropertyId);
3287             mDependentOnPropertyPermissions = dependentPropertyPermissions;
3288             return this;
3289         }
3290 
3291         /** Sets the possible config array values. */
setPossibleConfigArrayValues( ImmutableSet<Integer> possibleConfigArrayValues)3292         public Builder<T> setPossibleConfigArrayValues(
3293                 ImmutableSet<Integer> possibleConfigArrayValues) {
3294             mPossibleConfigArrayValues = possibleConfigArrayValues;
3295             return this;
3296         }
3297 
3298         /**
3299          * Used to assert that supportedEnum values provided in config are a subset of all possible
3300          * enum values that can be set for the property.
3301          */
setBitMapEnumEnabled(boolean enabled)3302         public Builder<T> setBitMapEnumEnabled(boolean enabled) {
3303             mEnumIsBitMap = enabled;
3304             return this;
3305         }
3306 
3307         /*
3308          * Used to assert that supportedEnum values provided in config are a subset of all possible
3309          * enum values that can be set for the property. If enums is defined as a bit map rather
3310          * than a regular integer, setBitMapEnumEnabled(boolean) should be used as well.
3311          */
setAllPossibleEnumValues(ImmutableSet<T> allPossibleEnumValues)3312         public Builder<T> setAllPossibleEnumValues(ImmutableSet<T> allPossibleEnumValues) {
3313             mAllPossibleEnumValues = allPossibleEnumValues;
3314             return this;
3315         }
3316 
3317         /**
3318          * Used to assert that certain values that must not be allowed to be written will throw an
3319          * IllegalArgumentException when we try to write them using setProperty.
3320          */
setAllPossibleUnwritableValues( ImmutableSet<T> allPossibleUnwritableValues)3321         public Builder<T> setAllPossibleUnwritableValues(
3322                 ImmutableSet<T> allPossibleUnwritableValues) {
3323             mAllPossibleUnwritableValues = allPossibleUnwritableValues;
3324             return this;
3325         }
3326 
3327         /**
3328          * Used to assert that certain values that are temporarily unavailable to be written will
3329          * throw a PropertyNotAvailableException when we try to write them using setProperty.
3330          */
setAllPossibleUnavailableValues( ImmutableSet<T> allPossibleUnavailableValues)3331         public Builder<T> setAllPossibleUnavailableValues(
3332                 ImmutableSet<T> allPossibleUnavailableValues) {
3333             mAllPossibleUnavailableValues = allPossibleUnavailableValues;
3334             return this;
3335         }
3336 
3337         /**
3338          * Requires that the property value must be one of the value defined in the config array.
3339          */
requirePropertyValueTobeInConfigArray()3340         public Builder<T> requirePropertyValueTobeInConfigArray() {
3341             mRequirePropertyValueToBeInConfigArray = true;
3342             return this;
3343         }
3344 
3345         /** Uses the config array values to set the property value. */
verifySetterWithConfigArrayValues()3346         public Builder<T> verifySetterWithConfigArrayValues() {
3347             mVerifySetterWithConfigArrayValues = true;
3348             return this;
3349         }
3350 
3351         /** Requires minValue and maxValue to be set. */
requireMinMaxValues()3352         public Builder<T> requireMinMaxValues() {
3353             mRequireMinMaxValues = true;
3354             return this;
3355         }
3356 
3357         /** Requires minValue to be 0. */
requireMinValuesToBeZero()3358         public Builder<T> requireMinValuesToBeZero() {
3359             mRequireMinValuesToBeZero = true;
3360             return this;
3361         }
3362 
3363         /** Requires 0 to be contains within minValue and maxValue. */
requireZeroToBeContainedInMinMaxRanges()3364         public Builder<T> requireZeroToBeContainedInMinMaxRanges() {
3365             mRequireZeroToBeContainedInMinMaxRanges = true;
3366             return this;
3367         }
3368 
3369         /** Sets that the property might depend on HVAC_POWER_ON. */
setPossiblyDependentOnHvacPowerOn()3370         public Builder<T> setPossiblyDependentOnHvacPowerOn() {
3371             mPossiblyDependentOnHvacPowerOn = true;
3372             return this;
3373         }
3374 
3375         /** Verifies if returning error state, the error state is expected. */
verifyErrorStates()3376         public Builder<T> verifyErrorStates() {
3377             mVerifyErrorStates = true;
3378             return this;
3379         }
3380 
3381         /** Sets the delay to wait for the power state to propagate to dependent properties. */
setPowerPropagationDelayMs(int delayMs)3382         public Builder<T> setPowerPropagationDelayMs(int delayMs) {
3383             mPowerPropagationDelayMs = delayMs;
3384             return this;
3385         }
3386 
3387         /** Adds the required read permission. */
addReadPermission(String readPermission)3388         public Builder<T> addReadPermission(String readPermission) {
3389             mReadPermissionsBuilder.add(readPermission);
3390             return this;
3391         }
3392 
3393         /**
3394          * Adds a single permission that alone can be used to update the property. Any set of
3395          * permissions in {@code mWritePermissionsBuilder} can be used to set the property.
3396          *
3397          * @param writePermission a permission used to update the property
3398          */
addWritePermission(String writePermission)3399         public Builder<T> addWritePermission(String writePermission) {
3400             mWritePermissionsBuilder.add(ImmutableSet.of(writePermission));
3401             return this;
3402         }
3403 
3404         /**
3405          * Adds a set of permissions that together can be used to update the property. Any set of
3406          * permissions in {@code mWritePermissionsBuilder} can be used to set the property.
3407          *
3408          * @param writePermissionSet a set of permissions that together can be used to update the
3409          *     property.
3410          */
addWritePermission(ImmutableSet<String> writePermissionSet)3411         public Builder<T> addWritePermission(ImmutableSet<String> writePermissionSet) {
3412             mWritePermissionsBuilder.add(writePermissionSet);
3413             return this;
3414         }
3415 
3416         /** Builds the verifier. */
build()3417         public VehiclePropertyVerifier<T> build() {
3418             return new VehiclePropertyVerifier<>(
3419                     mCarPropertyManager,
3420                     mPropertyId,
3421                     mAllowedAccessModes,
3422                     mAreaType,
3423                     mChangeMode,
3424                     mPropertyType,
3425                     mRequiredProperty,
3426                     mConfigArrayVerifier,
3427                     mCarPropertyValueVerifier,
3428                     mAreaIdsVerifier,
3429                     mCarPropertyConfigVerifier,
3430                     mDependentOnPropertyId,
3431                     mDependentOnPropertyPermissions,
3432                     mPossibleConfigArrayValues,
3433                     mEnumIsBitMap,
3434                     mAllPossibleEnumValues,
3435                     mAllPossibleUnwritableValues,
3436                     mAllPossibleUnavailableValues,
3437                     mRequirePropertyValueToBeInConfigArray,
3438                     mVerifySetterWithConfigArrayValues,
3439                     mRequireMinMaxValues,
3440                     mRequireMinValuesToBeZero,
3441                     mRequireZeroToBeContainedInMinMaxRanges,
3442                     mPossiblyDependentOnHvacPowerOn,
3443                     mVerifyErrorStates,
3444                     mPowerPropagationDelayMs,
3445                     mReadPermissionsBuilder.build(),
3446                     mWritePermissionsBuilder.build());
3447         }
3448     }
3449 
3450     private static class CarPropertyValueCallback
3451             implements CarPropertyManager.CarPropertyEventCallback {
3452         private final String mPropertyName;
3453         private final int[] mAreaIds;
3454         private final int mTotalCarPropertyValuesPerAreaId;
3455         private final CountDownLatch mCountDownLatch = new CountDownLatch(1);
3456         private final Object mLock = new Object();
3457 
3458         @GuardedBy("mLock")
3459         private final SparseArray<List<CarPropertyValue<?>>> mAreaIdToCarPropertyValues =
3460                 new SparseArray<>();
3461 
3462         private final long mTimeoutMillis;
3463 
CarPropertyValueCallback( String propertyName, int[] areaIds, int totalCarPropertyValuesPerAreaId, long timeoutMillis)3464         CarPropertyValueCallback(
3465                 String propertyName,
3466                 int[] areaIds,
3467                 int totalCarPropertyValuesPerAreaId,
3468                 long timeoutMillis) {
3469             mPropertyName = propertyName;
3470             mAreaIds = areaIds;
3471             mTotalCarPropertyValuesPerAreaId = totalCarPropertyValuesPerAreaId;
3472             mTimeoutMillis = timeoutMillis;
3473             synchronized (mLock) {
3474                 for (int areaId : mAreaIds) {
3475                     mAreaIdToCarPropertyValues.put(areaId, new ArrayList<>());
3476                 }
3477             }
3478         }
3479 
getAreaIdToCarPropertyValues()3480         public SparseArray<List<CarPropertyValue<?>>> getAreaIdToCarPropertyValues() {
3481             boolean awaitSuccess = false;
3482             try {
3483                 awaitSuccess = mCountDownLatch.await(mTimeoutMillis, TimeUnit.MILLISECONDS);
3484             } catch (InterruptedException e) {
3485                 assertWithMessage(
3486                                 "Waiting for onChangeEvent callback(s) for "
3487                                         + mPropertyName
3488                                         + " threw an exception: "
3489                                         + e)
3490                         .fail();
3491             }
3492             synchronized (mLock) {
3493                 assertWithMessage(
3494                                 "Never received "
3495                                         + mTotalCarPropertyValuesPerAreaId
3496                                         + "  CarPropertyValues for all "
3497                                         + mPropertyName
3498                                         + "'s areaIds: "
3499                                         + Arrays.toString(mAreaIds)
3500                                         + " before "
3501                                         + mTimeoutMillis
3502                                         + " ms timeout - "
3503                                         + mAreaIdToCarPropertyValues)
3504                         .that(awaitSuccess)
3505                         .isTrue();
3506                 return mAreaIdToCarPropertyValues.clone();
3507             }
3508         }
3509 
3510         @Override
onChangeEvent(CarPropertyValue carPropertyValue)3511         public void onChangeEvent(CarPropertyValue carPropertyValue) {
3512             synchronized (mLock) {
3513                 if (hasEnoughCarPropertyValuesForEachAreaIdLocked()) {
3514                     return;
3515                 }
3516                 mAreaIdToCarPropertyValues.get(carPropertyValue.getAreaId()).add(carPropertyValue);
3517                 if (hasEnoughCarPropertyValuesForEachAreaIdLocked()) {
3518                     mCountDownLatch.countDown();
3519                 }
3520             }
3521         }
3522 
3523         @GuardedBy("mLock")
hasEnoughCarPropertyValuesForEachAreaIdLocked()3524         private boolean hasEnoughCarPropertyValuesForEachAreaIdLocked() {
3525             for (int areaId : mAreaIds) {
3526                 List<CarPropertyValue<?>> carPropertyValues =
3527                         mAreaIdToCarPropertyValues.get(areaId);
3528                 if (carPropertyValues == null
3529                         || carPropertyValues.size() < mTotalCarPropertyValuesPerAreaId) {
3530                     return false;
3531                 }
3532             }
3533             return true;
3534         }
3535 
3536         @Override
onErrorEvent(int propId, int zone)3537         public void onErrorEvent(int propId, int zone) {}
3538 
3539         @Override
onErrorEvent(int propId, int areaId, int errorCode)3540         public void onErrorEvent(int propId, int areaId, int errorCode) {}
3541     }
3542 
3543     private static class SetterCallback<T> implements CarPropertyManager.CarPropertyEventCallback {
3544         private final int mPropertyId;
3545         private final String mPropertyName;
3546         private final int mAreaId;
3547         private final T mExpectedSetValue;
3548         private final CountDownLatch mCountDownLatch = new CountDownLatch(1);
3549         private final long mCreationTimeNanos = SystemClock.elapsedRealtimeNanos();
3550         private CarPropertyValue<?> mUpdatedCarPropertyValue = null;
3551         private T mReceivedValue = null;
3552         private Integer mSetErrorCode = null;
3553 
SetterCallback(int propertyId, int areaId, T expectedSetValue)3554         SetterCallback(int propertyId, int areaId, T expectedSetValue) {
3555             mPropertyId = propertyId;
3556             mPropertyName = VehiclePropertyIds.toString(propertyId);
3557             mAreaId = areaId;
3558             mExpectedSetValue = expectedSetValue;
3559         }
3560 
valueToString(T value)3561         private String valueToString(T value) {
3562             if (value.getClass().isArray()) {
3563                 return Arrays.toString((Object[]) value);
3564             }
3565             return value.toString();
3566         }
3567 
3568         /**
3569          * Waits at most {@code timeoutInSec} for a property event that is the result of a {@code
3570          * setProperty} request.
3571          *
3572          * <p>If {@link #onChangeEvent(CarPropertyValue)} is called, then this method will return
3573          * the {@link CarPropertyValue} if:
3574          *
3575          * <ul>
3576          *   <li>The property ID and area ID match {@link #mPropertyId} and {@link #mAreaId}
3577          *   <li>The event is timestamped after {@link #mCreationTimeNanos} but before {@link
3578          *       SystemClock#elapsedRealtimeNanos()}
3579          *   <li>One of the following is true:
3580          *       <ul>
3581          *         <li>{@link CarPropertyValue#getStatus()} is NOT {@link
3582          *             CarPropertyValue#STATUS_AVAILABLE}
3583          *         <li>{@link CarPropertyValue#getStatus()} is {@link
3584          *             CarPropertyValue#STATUS_AVAILABLE} and {@link CarPropertyValue#getValue()}
3585          *             equals {@link #mExpectedSetValue}.
3586          *       </ul>
3587          * </ul>
3588          *
3589          * <p>If {@link #onErrorEvent(int, int)} is called, then this method will return {@code
3590          * null} if:
3591          *
3592          * <ul>
3593          *   <li>The property ID and area ID match {@link #mPropertyId} and {@link #mAreaId}
3594          * </ul>
3595          *
3596          * <p>If {@code timeoutInSec} is reached before any of the above conditions are met or if
3597          * {@link InterruptedException} is thrown, then this method will throw an {@code
3598          * AssertionError} and fail the test.
3599          *
3600          * <p>If the callback receives a valid CarPropertyValue and a valid set property error code,
3601          * then an {@code AssertionError} will be thrown and the test will fail.
3602          *
3603          * @param timeoutInSec maximum time in seconds to wait for an expected event
3604          * @return a valid {@link CarPropertyValue} if all {@link #onChangeEvent(CarPropertyValue)}
3605          *     conditions are met, or {@code null} if all {@link #onErrorEvent(int, int)} conditions
3606          *     are met.
3607          */
waitForPropertyEvent(int timeoutInSec)3608         public CarPropertyValue<?> waitForPropertyEvent(int timeoutInSec) {
3609             try {
3610                 assertWithMessage(
3611                                 "Never received onChangeEvent(s) or onErrorEvent(s) for "
3612                                         + mPropertyName
3613                                         + " new value: "
3614                                         + valueToString(mExpectedSetValue)
3615                                         + " before"
3616                                         + " timeout. Received: "
3617                                         + (mReceivedValue == null
3618                                                 ? "No value"
3619                                                 : valueToString(mReceivedValue)))
3620                         .that(mCountDownLatch.await(timeoutInSec, TimeUnit.SECONDS))
3621                         .isTrue();
3622             } catch (InterruptedException e) {
3623                 assertWithMessage(
3624                                 "Waiting for onChangeEvent set callback for "
3625                                         + mPropertyName
3626                                         + " threw an exception: "
3627                                         + e)
3628                         .fail();
3629             }
3630             assertWithMessage(
3631                             "Received both a set error code"
3632                                     + mSetErrorCode
3633                                     + " and a new value: "
3634                                     + mUpdatedCarPropertyValue
3635                                     + " for propertyId: "
3636                                     + mPropertyName
3637                                     + " areaId: "
3638                                     + mAreaId)
3639                     .that(mUpdatedCarPropertyValue != null && mSetErrorCode != null)
3640                     .isFalse();
3641             return mUpdatedCarPropertyValue;
3642         }
3643 
3644         @Override
onChangeEvent(CarPropertyValue carPropertyValue)3645         public void onChangeEvent(CarPropertyValue carPropertyValue) {
3646             // Checking whether the updated carPropertyValue is caused by the setProperty request.
3647             if (mUpdatedCarPropertyValue != null
3648                     || carPropertyValue.getPropertyId() != mPropertyId
3649                     || carPropertyValue.getAreaId() != mAreaId
3650                     || carPropertyValue.getTimestamp() <= mCreationTimeNanos
3651                     || carPropertyValue.getTimestamp() >= SystemClock.elapsedRealtimeNanos()) {
3652                 return;
3653             }
3654             mReceivedValue = (T) carPropertyValue.getValue();
3655             if (carPropertyValue.getStatus() == CarPropertyValue.STATUS_AVAILABLE
3656                     && !valueEquals(mExpectedSetValue, mReceivedValue)) {
3657                 return;
3658             }
3659             mUpdatedCarPropertyValue = carPropertyValue;
3660             mCountDownLatch.countDown();
3661         }
3662 
3663         @Override
onErrorEvent(int propId, int areaId)3664         public void onErrorEvent(int propId, int areaId) {
3665             onErrorEvent(propId, areaId, CarPropertyManager.CAR_SET_PROPERTY_ERROR_CODE_UNKNOWN);
3666         }
3667 
3668         @Override
onErrorEvent(int propertyId, int areaId, int errorCode)3669         public void onErrorEvent(int propertyId, int areaId, int errorCode) {
3670             if (mCountDownLatch.getCount() == 0) {
3671                 Log.w(
3672                         TAG,
3673                         "SetterCallback - Dropping onErrorEvent. Received an onErrorEvent call "
3674                                 + " after the CountDownLatch finished - "
3675                                 + "propertyId: "
3676                                 + VehiclePropertyIds.toString(propertyId)
3677                                 + " areaId: "
3678                                 + areaId
3679                                 + " errorCode: "
3680                                 + errorCode);
3681                 return;
3682             }
3683             if (propertyId != mPropertyId) {
3684                 Log.w(
3685                         TAG,
3686                         "SetterCallback - Dropping onErrorEvent. Property ID does not match"
3687                                 + " expected property ID: "
3688                                 + mPropertyName
3689                                 + " - propertyId: "
3690                                 + VehiclePropertyIds.toString(propertyId)
3691                                 + " areaId: "
3692                                 + areaId
3693                                 + " errorCode: "
3694                                 + errorCode);
3695                 return;
3696             }
3697             if (areaId != mAreaId) {
3698                 Log.w(
3699                         TAG,
3700                         "SetterCallback - Dropping onErrorEvent. Area ID does not match expected"
3701                                 + " area ID: "
3702                                 + mAreaId
3703                                 + " - propertyId: "
3704                                 + mPropertyName
3705                                 + " areaId: "
3706                                 + areaId
3707                                 + " errorCode: "
3708                                 + errorCode);
3709                 return;
3710             }
3711             if (!VALID_SET_ERROR_CODES.contains(errorCode)) {
3712                 Log.w(
3713                         TAG,
3714                         "SetterCallback - Dropping onErrorEvent. errorCode is not a valid error"
3715                                 + " code: "
3716                                 + VALID_SET_ERROR_CODES
3717                                 + " - propertyId: "
3718                                 + mPropertyName
3719                                 + " areaId: "
3720                                 + areaId
3721                                 + " errorCode: "
3722                                 + errorCode);
3723                 return;
3724             }
3725             if (mSetErrorCode != null) {
3726                 Log.w(
3727                         TAG,
3728                         "SetterCallback - Dropping onErrorEvent. Already received a valid"
3729                                 + " errorCode: "
3730                                 + mSetErrorCode
3731                                 + " - propertyId: "
3732                                 + mPropertyName
3733                                 + " areaId: "
3734                                 + areaId
3735                                 + " errorCode: "
3736                                 + errorCode);
3737                 return;
3738             }
3739             Log.w(
3740                     TAG,
3741                     "SetterCallback - Received setProperty error code: "
3742                             + errorCode
3743                             + " - propertyId: "
3744                             + mPropertyName
3745                             + " - areaId: "
3746                             + areaId);
3747             mSetErrorCode = Integer.valueOf(errorCode);
3748             mCountDownLatch.countDown();
3749         }
3750     }
3751 
valueEquals(V v1, V v2)3752     private static <V> boolean valueEquals(V v1, V v2) {
3753         return (v1 instanceof Float && floatEquals((Float) v1, (Float) v2))
3754                 || (v1 instanceof Float[] && floatArrayEquals((Float[]) v1, (Float[]) v2))
3755                 || (v1 instanceof Long[] && longArrayEquals((Long[]) v1, (Long[]) v2))
3756                 || (v1 instanceof Integer[] && integerArrayEquals((Integer[]) v1, (Integer[]) v2))
3757                 || v1.equals(v2);
3758     }
3759 
floatEquals(float f1, float f2)3760     private static boolean floatEquals(float f1, float f2) {
3761         return Math.abs(f1 - f2) < FLOAT_INEQUALITY_THRESHOLD;
3762     }
3763 
floatArrayEquals(Float[] f1, Float[] f2)3764     private static boolean floatArrayEquals(Float[] f1, Float[] f2) {
3765         return Arrays.equals(f1, f2);
3766     }
3767 
longArrayEquals(Long[] l1, Long[] l2)3768     private static boolean longArrayEquals(Long[] l1, Long[] l2) {
3769         return Arrays.equals(l1, l2);
3770     }
3771 
integerArrayEquals(Integer[] i1, Integer[] i2)3772     private static boolean integerArrayEquals(Integer[] i1, Integer[] i2) {
3773         return Arrays.equals(i1, i2);
3774     }
3775 
3776     private static class TestGetPropertyCallback implements GetPropertyCallback {
3777         private final CountDownLatch mCountDownLatch;
3778         private final int mGetPropertyResultsCount;
3779         private final Object mLock = new Object();
3780 
3781         @GuardedBy("mLock")
3782         private final List<GetPropertyResult<?>> mGetPropertyResults = new ArrayList<>();
3783 
3784         @GuardedBy("mLock")
3785         private final List<PropertyAsyncError> mPropertyAsyncErrors = new ArrayList<>();
3786 
waitForResults()3787         public void waitForResults() {
3788             try {
3789                 assertWithMessage(
3790                                 "Received "
3791                                         + (mGetPropertyResultsCount - mCountDownLatch.getCount())
3792                                         + " onSuccess(s), expected "
3793                                         + mGetPropertyResultsCount
3794                                         + " onSuccess(s)")
3795                         .that(mCountDownLatch.await(5, TimeUnit.SECONDS))
3796                         .isTrue();
3797             } catch (InterruptedException e) {
3798                 assertWithMessage("Waiting for onSuccess threw an exception: " + e).fail();
3799             }
3800         }
3801 
getGetPropertyResults()3802         public List<GetPropertyResult<?>> getGetPropertyResults() {
3803             synchronized (mLock) {
3804                 return mGetPropertyResults;
3805             }
3806         }
3807 
getPropertyAsyncErrors()3808         public List<PropertyAsyncError> getPropertyAsyncErrors() {
3809             synchronized (mLock) {
3810                 return mPropertyAsyncErrors;
3811             }
3812         }
3813 
3814         @Override
onSuccess(GetPropertyResult getPropertyResult)3815         public void onSuccess(GetPropertyResult getPropertyResult) {
3816             synchronized (mLock) {
3817                 mGetPropertyResults.add(getPropertyResult);
3818                 mCountDownLatch.countDown();
3819             }
3820         }
3821 
3822         @Override
onFailure(PropertyAsyncError propertyAsyncError)3823         public void onFailure(PropertyAsyncError propertyAsyncError) {
3824             synchronized (mLock) {
3825                 mPropertyAsyncErrors.add(propertyAsyncError);
3826                 mCountDownLatch.countDown();
3827             }
3828         }
3829 
TestGetPropertyCallback(int getPropertyResultsCount)3830         TestGetPropertyCallback(int getPropertyResultsCount) {
3831             mCountDownLatch = new CountDownLatch(getPropertyResultsCount);
3832             mGetPropertyResultsCount = getPropertyResultsCount;
3833         }
3834     }
3835 
3836     private static class TestSetPropertyCallback implements SetPropertyCallback {
3837         private final CountDownLatch mCountDownLatch;
3838         private final int mSetPropertyResultsCount;
3839         private final Object mLock = new Object();
3840 
3841         @GuardedBy("mLock")
3842         private final List<SetPropertyResult> mSetPropertyResults = new ArrayList<>();
3843 
3844         @GuardedBy("mLock")
3845         private final List<PropertyAsyncError> mPropertyAsyncErrors = new ArrayList<>();
3846 
waitForResults()3847         public void waitForResults() {
3848             try {
3849                 assertWithMessage(
3850                                 "Received "
3851                                         + (mSetPropertyResultsCount - mCountDownLatch.getCount())
3852                                         + " onSuccess(s), expected "
3853                                         + mSetPropertyResultsCount
3854                                         + " onSuccess(s)")
3855                         .that(mCountDownLatch.await(5, TimeUnit.SECONDS))
3856                         .isTrue();
3857             } catch (InterruptedException e) {
3858                 assertWithMessage("Waiting for onSuccess threw an exception: " + e).fail();
3859             }
3860         }
3861 
getSetPropertyResults()3862         public List<SetPropertyResult> getSetPropertyResults() {
3863             synchronized (mLock) {
3864                 return mSetPropertyResults;
3865             }
3866         }
3867 
getPropertyAsyncErrors()3868         public List<PropertyAsyncError> getPropertyAsyncErrors() {
3869             synchronized (mLock) {
3870                 return mPropertyAsyncErrors;
3871             }
3872         }
3873 
3874         @Override
onSuccess(SetPropertyResult setPropertyResult)3875         public void onSuccess(SetPropertyResult setPropertyResult) {
3876             synchronized (mLock) {
3877                 mSetPropertyResults.add(setPropertyResult);
3878                 mCountDownLatch.countDown();
3879             }
3880         }
3881 
3882         @Override
onFailure(PropertyAsyncError propertyAsyncError)3883         public void onFailure(PropertyAsyncError propertyAsyncError) {
3884             synchronized (mLock) {
3885                 mPropertyAsyncErrors.add(propertyAsyncError);
3886                 mCountDownLatch.countDown();
3887             }
3888         }
3889 
TestSetPropertyCallback(int setPropertyResultsCount)3890         TestSetPropertyCallback(int setPropertyResultsCount) {
3891             mCountDownLatch = new CountDownLatch(setPropertyResultsCount);
3892             mSetPropertyResultsCount = setPropertyResultsCount;
3893         }
3894     }
3895 
verifyGetPropertiesAsync()3896     private void verifyGetPropertiesAsync() {
3897         if (!isAtLeastU()) {
3898             return;
3899         }
3900         CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig();
3901         if (!AREA_ID_CONFIG_ACCESS_FLAG && !canRead(carPropertyConfig.getAccess())) {
3902             verifyGetPropertiesAsyncFails(carPropertyConfig.getAreaIds()[0]);
3903             return;
3904         }
3905 
3906         List<GetPropertyRequest> getPropertyRequests = new ArrayList<>();
3907         SparseIntArray requestIdToAreaIdMap = new SparseIntArray();
3908         for (AreaIdConfig<?> areaIdConfig : carPropertyConfig.getAreaIdConfigs()) {
3909             int areaId = areaIdConfig.getAreaId();
3910             if (!canRead(carPropertyConfig, areaId)) {
3911                 verifyGetPropertiesAsyncFails(areaId);
3912                 continue;
3913             }
3914             GetPropertyRequest getPropertyRequest =
3915                     mCarPropertyManager.generateGetPropertyRequest(mPropertyId, areaId);
3916             int requestId = getPropertyRequest.getRequestId();
3917             requestIdToAreaIdMap.put(requestId, areaId);
3918             getPropertyRequests.add(getPropertyRequest);
3919         }
3920 
3921         TestGetPropertyCallback testGetPropertyCallback =
3922                 new TestGetPropertyCallback(requestIdToAreaIdMap.size());
3923         mCarPropertyManager.getPropertiesAsync(
3924                 getPropertyRequests, /* cancellationSignal: */
3925                 null,
3926                 /* callbackExecutor: */ null,
3927                 testGetPropertyCallback);
3928         testGetPropertyCallback.waitForResults();
3929 
3930         for (GetPropertyResult<?> getPropertyResult :
3931                 testGetPropertyCallback.getGetPropertyResults()) {
3932             int requestId = getPropertyResult.getRequestId();
3933             int propertyId = getPropertyResult.getPropertyId();
3934             if (requestIdToAreaIdMap.indexOfKey(requestId) < 0) {
3935                 assertWithMessage(
3936                                 "getPropertiesAsync received GetPropertyResult with unknown"
3937                                         + " requestId: "
3938                                         + getPropertyResult)
3939                         .fail();
3940             }
3941             Integer expectedAreaId = requestIdToAreaIdMap.get(requestId);
3942             verifyCarPropertyValue(
3943                     propertyId,
3944                     getPropertyResult.getAreaId(),
3945                     CarPropertyValue.STATUS_AVAILABLE,
3946                     getPropertyResult.getTimestampNanos(),
3947                     (T) getPropertyResult.getValue(),
3948                     expectedAreaId,
3949                     CAR_PROPERTY_VALUE_SOURCE_CALLBACK);
3950         }
3951 
3952         for (PropertyAsyncError propertyAsyncError :
3953                 testGetPropertyCallback.getPropertyAsyncErrors()) {
3954             int requestId = propertyAsyncError.getRequestId();
3955             // Async errors are ok as long the requestId is valid
3956             if (requestIdToAreaIdMap.indexOfKey(requestId) < 0) {
3957                 assertWithMessage(
3958                                 "getPropertiesAsync received PropertyAsyncError with unknown"
3959                                         + " requestId: "
3960                                         + propertyAsyncError)
3961                         .fail();
3962             }
3963         }
3964     }
3965 
verifyGetPropertiesAsyncFails(int areaId)3966     private void verifyGetPropertiesAsyncFails(int areaId) {
3967         if (!isAtLeastU()) {
3968             return;
3969         }
3970         List<GetPropertyRequest> getPropertyRequests = new ArrayList<>();
3971         GetPropertyRequest getPropertyRequest =
3972                 mCarPropertyManager.generateGetPropertyRequest(mPropertyId, areaId);
3973         getPropertyRequests.add(getPropertyRequest);
3974         TestGetPropertyCallback testGetPropertyCallback =
3975                 new TestGetPropertyCallback(/* getPropertyResultsCount: */ 1);
3976         assertThrows(
3977                 mPropertyName
3978                         + " is a write_only property so getPropertiesAsync should throw an"
3979                         + " IllegalArgumentException.",
3980                 IllegalArgumentException.class,
3981                 () ->
3982                         mCarPropertyManager.getPropertiesAsync(
3983                                 getPropertyRequests,
3984                                 /* cancellationSignal: */ null, /* callbackExecutor: */
3985                                 null,
3986                                 testGetPropertyCallback));
3987     }
3988 
verifySetPropertiesAsync()3989     private void verifySetPropertiesAsync() {
3990         if (!isAtLeastU()) {
3991             return;
3992         }
3993         CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig();
3994         if (!AREA_ID_CONFIG_ACCESS_FLAG && !canWrite(carPropertyConfig.getAccess())) {
3995             verifySetPropertiesAsyncFails(carPropertyConfig.getAreaIds()[0]);
3996             return;
3997         }
3998 
3999         ArrayMap<Integer, List<T>> areaIdToPossibleValuesMap = new ArrayMap<>();
4000         // The maximum possible values count for all areaIds.
4001         int maxPossibleValuesCount = 0;
4002 
4003         for (AreaIdConfig<?> areaIdConfig : carPropertyConfig.getAreaIdConfigs()) {
4004             int areaId = areaIdConfig.getAreaId();
4005             Collection<T> possibleValues = getPossibleValues(areaId);
4006             if (possibleValues == null || possibleValues.size() == 0) {
4007                 continue;
4008             }
4009             // Convert to a list so that we can access via index later.
4010             areaIdToPossibleValuesMap.put(areaId, new ArrayList<T>(possibleValues));
4011             if (possibleValues.size() > maxPossibleValuesCount) {
4012                 maxPossibleValuesCount = possibleValues.size();
4013             }
4014         }
4015 
4016         // For each possible value index, generate one async request containing all areaIds that has
4017         // possible values defined.
4018         // For example, [value0ForArea1, value0ForArea2], [value1ForArea1, value1ForArea2].
4019         // If we run out of possible values for one areaId, just use the last possible value for
4020         // that areaId.
4021         for (int i = 0; i < maxPossibleValuesCount; i++) {
4022             SparseIntArray requestIdToAreaIdMap = new SparseIntArray();
4023             List<SetPropertyRequest<?>> setPropertyRequests = new ArrayList<>();
4024             for (AreaIdConfig<?> areaIdConfig : carPropertyConfig.getAreaIdConfigs()) {
4025                 int areaId = areaIdConfig.getAreaId();
4026                 if (!canWrite(carPropertyConfig, areaId)) {
4027                     verifySetPropertiesAsyncFails(areaId);
4028                     continue;
4029                 }
4030                 if (!areaIdToPossibleValuesMap.containsKey(areaId)) {
4031                     continue;
4032                 }
4033                 List<T> possibleValues = areaIdToPossibleValuesMap.get(areaId);
4034                 // Always use the last possible value if we run out of possible values.
4035                 int index = Math.min(i, possibleValues.size() - 1);
4036                 SetPropertyRequest setPropertyRequest =
4037                         mCarPropertyManager.generateSetPropertyRequest(
4038                                 mPropertyId, areaId, possibleValues.get(index));
4039                 if (!canRead(carPropertyConfig, areaId)) {
4040                     setPropertyRequest.setWaitForPropertyUpdate(false);
4041                 }
4042                 requestIdToAreaIdMap.put(setPropertyRequest.getRequestId(), areaId);
4043                 setPropertyRequests.add(setPropertyRequest);
4044             }
4045 
4046             TestSetPropertyCallback testSetPropertyCallback =
4047                     new TestSetPropertyCallback(requestIdToAreaIdMap.size());
4048             mCarPropertyManager.setPropertiesAsync(
4049                     setPropertyRequests,
4050                     /* cancellationSignal: */ null, /* callbackExecutor: */
4051                     null,
4052                     testSetPropertyCallback);
4053             testSetPropertyCallback.waitForResults();
4054 
4055             for (SetPropertyResult setPropertyResult :
4056                     testSetPropertyCallback.getSetPropertyResults()) {
4057                 int requestId = setPropertyResult.getRequestId();
4058                 if (requestIdToAreaIdMap.indexOfKey(requestId) < 0) {
4059                     assertWithMessage(
4060                                     "setPropertiesAsync received SetPropertyResult with unknown"
4061                                             + " requestId: "
4062                                             + setPropertyResult)
4063                             .fail();
4064                 }
4065                 assertThat(setPropertyResult.getPropertyId()).isEqualTo(mPropertyId);
4066                 assertThat(setPropertyResult.getAreaId())
4067                         .isEqualTo(requestIdToAreaIdMap.get(requestId));
4068                 assertThat(setPropertyResult.getUpdateTimestampNanos()).isAtLeast(0);
4069                 assertThat(setPropertyResult.getUpdateTimestampNanos())
4070                         .isLessThan(SystemClock.elapsedRealtimeNanos());
4071             }
4072 
4073             for (PropertyAsyncError propertyAsyncError :
4074                     testSetPropertyCallback.getPropertyAsyncErrors()) {
4075                 int requestId = propertyAsyncError.getRequestId();
4076                 // Async errors are ok as long the requestId is valid
4077                 if (requestIdToAreaIdMap.indexOfKey(requestId) < 0) {
4078                     assertWithMessage(
4079                                     "setPropertiesAsync received PropertyAsyncError with unknown "
4080                                             + "requestId: "
4081                                             + propertyAsyncError)
4082                             .fail();
4083                 }
4084             }
4085         }
4086     }
4087 
verifySetPropertiesAsyncFails(int areaId)4088     private void verifySetPropertiesAsyncFails(int areaId) {
4089         if (!isAtLeastU()) {
4090             return;
4091         }
4092         CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig();
4093         List<SetPropertyRequest<?>> setPropertyRequests = new ArrayList<>();
4094         SetPropertyRequest setPropertyRequest =
4095                 mCarPropertyManager.generateSetPropertyRequest(
4096                         mPropertyId, areaId, getDefaultValue(carPropertyConfig.getPropertyType()));
4097         setPropertyRequests.add(setPropertyRequest);
4098         TestSetPropertyCallback testSetPropertyCallback =
4099                 new TestSetPropertyCallback(/* setPropertyResultsCount: */ 1);
4100         assertThrows(
4101                 mPropertyName
4102                         + " is a read_only property so setPropertiesAsync should throw an"
4103                         + " IllegalArgumentException.",
4104                 IllegalArgumentException.class,
4105                 () ->
4106                         mCarPropertyManager.setPropertiesAsync(
4107                                 setPropertyRequests,
4108                                 /* cancellationSignal: */ null, /* callbackExecutor: */
4109                                 null,
4110                                 testSetPropertyCallback));
4111     }
4112 
verifyGetMinMaxSupportedValue()4113     private void verifyGetMinMaxSupportedValue() {
4114         CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig();
4115         int[] areaIds = carPropertyConfig.getAreaIds();
4116         for (int areaId : areaIds) {
4117             // Because min/max supported value is dynamic, the value we got here is not necessarily
4118             // the same as the value we got from VehicleAreaConfig.
4119             MinMaxSupportedValue<T> minMaxSupportedValue =
4120                     mCarPropertyManager.getMinMaxSupportedValue(mPropertyId, areaId);
4121             AreaIdConfig areaIdConfig = carPropertyConfig.getAreaIdConfig(areaId);
4122             T areaIdMinValue = minMaxSupportedValue.getMinValue();
4123             T areaIdMaxValue = minMaxSupportedValue.getMaxValue();
4124             if (areaIdConfig.hasMinSupportedValue()) {
4125                 assertWithMessage(
4126                                 mPropertyName
4127                                         + " - area ID: "
4128                                         + areaId
4129                                         + " minSupportedValue must not be null if"
4130                                         + " hasMinSupportedValue is true")
4131                         .that(areaIdMinValue)
4132                         .isNotNull();
4133             } else {
4134                 assertWithMessage(
4135                                 mPropertyName
4136                                         + " - area ID: "
4137                                         + areaId
4138                                         + " minSupportedValue must be null if hasMinSupportedValue"
4139                                         + " is false")
4140                         .that(areaIdMinValue)
4141                         .isNull();
4142             }
4143 
4144             if (areaIdConfig.hasMaxSupportedValue()) {
4145                 assertWithMessage(
4146                                 mPropertyName
4147                                         + " - area ID: "
4148                                         + areaId
4149                                         + " maxSupportedValue must not be null if"
4150                                         + " hasMaxSupportedValue is true")
4151                         .that(areaIdMaxValue)
4152                         .isNotNull();
4153             } else {
4154                 assertWithMessage(
4155                                 mPropertyName
4156                                         + " - area ID: "
4157                                         + areaId
4158                                         + " maxSupportedValue must be null if hasMaxSupportedValue"
4159                                         + " is false")
4160                         .that(areaIdMaxValue)
4161                         .isNull();
4162             }
4163             if (mRequireMinValuesToBeZero) {
4164                 assertWithMessage(
4165                                 mPropertyName + " - area ID: " + areaId + " min value must be zero")
4166                         .that(areaIdMinValue)
4167                         .isEqualTo(0);
4168             }
4169             if (mRequireZeroToBeContainedInMinMaxRanges) {
4170                 assertWithMessage(
4171                                 mPropertyName
4172                                         + " - areaId: "
4173                                         + areaId
4174                                         + "'s max and min range must contain zero")
4175                         .that(verifyMaxAndMinRangeContainsZero(areaIdMinValue, areaIdMaxValue))
4176                         .isTrue();
4177             }
4178             if (areaIdMinValue != null || areaIdMaxValue != null) {
4179                 assertWithMessage(
4180                                 mPropertyName
4181                                         + " - areaId: "
4182                                         + areaId
4183                                         + "'s max value must be >= min value")
4184                         .that(verifyMaxAndMin(areaIdMinValue, areaIdMaxValue))
4185                         .isTrue();
4186             }
4187         }
4188     }
4189 
verifyGetSupportedValuesList()4190     private void verifyGetSupportedValuesList() {
4191         CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig();
4192         int[] areaIds = carPropertyConfig.getAreaIds();
4193         Class propertyType = carPropertyConfig.getPropertyType();
4194         for (int areaId : areaIds) {
4195             List<T> supportedValuesList =
4196                     mCarPropertyManager.getSupportedValuesList(mPropertyId, areaId);
4197             AreaIdConfig areaIdConfig = carPropertyConfig.getAreaIdConfig(areaId);
4198             if (areaIdConfig.hasSupportedValuesList()) {
4199                 assertWithMessage(
4200                                 mPropertyName
4201                                         + " - area ID: "
4202                                         + areaId
4203                                         + " supportedValuesList must not be null if"
4204                                         + " hasSupportedValuesList is true")
4205                         .that(supportedValuesList)
4206                         .isNotNull();
4207             } else {
4208                 assertWithMessage(
4209                                 mPropertyName
4210                                         + " - area ID: "
4211                                         + areaId
4212                                         + " supportedValuesList must be null if"
4213                                         + " hasSupportedValuesList is false")
4214                         .that(supportedValuesList)
4215                         .isNull();
4216                 continue;
4217             }
4218 
4219             assertWithMessage(
4220                             mPropertyName
4221                                     + " - area ID: "
4222                                     + areaId
4223                                     + " supportedValuesList must not "
4224                                     + "contain duplicate elements")
4225                     .that(supportedValuesList)
4226                     .containsNoDuplicates();
4227 
4228             if (propertyType.equals(Integer.class)
4229                     || propertyType.equals(Float.class)
4230                     || propertyType.equals(Long.class)) {
4231                 assertWithMessage(
4232                                 mPropertyName
4233                                         + " - area ID: "
4234                                         + areaId
4235                                         + " supportedValuesList must be "
4236                                         + "in ascending order")
4237                         .that(supportedValuesList)
4238                         .isInOrder();
4239             }
4240 
4241             if (!mAllPossibleEnumValues.isEmpty()) {
4242                 for (int i = 0; i < supportedValuesList.size(); i++) {
4243                     T supportedValue = supportedValuesList.get(i);
4244                     assertWithMessage(
4245                                     mPropertyName
4246                                             + " - area ID: "
4247                                             + areaId
4248                                             + " supported value: "
4249                                             + supportedValue
4250                                             + " is not one of the possible enums")
4251                             .that(mAllPossibleEnumValues)
4252                             .contains(supportedValue);
4253                 }
4254             }
4255         }
4256     }
4257 
verifyRegisterUnregisterSupportedValuesChangeCallback()4258     private void verifyRegisterUnregisterSupportedValuesChangeCallback() {
4259         // Do nothing for the callback.
4260         SupportedValuesChangeCallback callback = (propertyId, areaId) -> {};
4261         // This is an executor that runs inside the test thread.
4262         Executor executor = (r) -> r.run();
4263 
4264         // We are not expecting any supported values change to happen, so here we just call the
4265         // the API and verify it succeed.
4266         assertThat(mCarPropertyManager.registerSupportedValuesChangeCallback(mPropertyId, callback))
4267                 .isTrue();
4268 
4269         mCarPropertyManager.unregisterSupportedValuesChangeCallback(mPropertyId);
4270 
4271         assertThat(
4272                         mCarPropertyManager.registerSupportedValuesChangeCallback(
4273                                 mPropertyId, executor, callback))
4274                 .isTrue();
4275 
4276         mCarPropertyManager.unregisterSupportedValuesChangeCallback(mPropertyId, callback);
4277 
4278         CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig();
4279         int[] areaIds = carPropertyConfig.getAreaIds();
4280         for (int areaId : areaIds) {
4281             assertThat(
4282                             mCarPropertyManager.registerSupportedValuesChangeCallback(
4283                                     mPropertyId, areaId, callback))
4284                     .isTrue();
4285 
4286             mCarPropertyManager.unregisterSupportedValuesChangeCallback(
4287                     mPropertyId, areaId, callback);
4288 
4289             assertThat(
4290                             mCarPropertyManager.registerSupportedValuesChangeCallback(
4291                                     mPropertyId, areaId, executor, callback))
4292                     .isTrue();
4293 
4294             mCarPropertyManager.unregisterSupportedValuesChangeCallback(
4295                     mPropertyId, areaId, callback);
4296         }
4297     }
4298 
verifyIsPropertyAvailable()4299     private void verifyIsPropertyAvailable() {
4300         runWithShellPermissionIdentity(
4301                 () -> verifyIsPropertyAvailableWithReadPermission(),
4302                 CHECK_MODE_ASSUME,
4303                 mReadPermissions.toArray(new String[0]));
4304     }
4305 
verifyIsPropertyAvailableWithReadPermission()4306     private void verifyIsPropertyAvailableWithReadPermission() {
4307         CarPropertyConfig<T> carPropertyConfig = getCarPropertyConfig();
4308         int[] areaIds;
4309         if (carPropertyConfig == null) {
4310             areaIds = new int[] {0};
4311         } else {
4312             areaIds = carPropertyConfig.getAreaIds();
4313         }
4314         for (int areaId : areaIds) {
4315             // Before Android B, isPropertyAvailable might throw
4316             // IllegalArgumentException if the property is not supported or the property is
4317             // not readable.
4318             if (!isAtLeastB()) {
4319                 boolean noReadAccess = false;
4320                 String reason = "";
4321                 if (carPropertyConfig != null) {
4322                     int access = getAreaIdAccessOrElseGlobalAccess(carPropertyConfig, areaId);
4323                     if (access == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE
4324                             || access == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_NONE) {
4325                         noReadAccess = true;
4326                         reason = "the property is not readable";
4327                     }
4328                 } else {
4329                     reason = "the property is not supported";
4330                 }
4331 
4332                 if (carPropertyConfig == null || noReadAccess) {
4333                     String errorMsg =
4334                             "carPropertyManager.isPropertyAvailable must return "
4335                                     + "false or throw IllegalArgumentException if "
4336                                     + reason
4337                                     + ", propertyId: "
4338                                     + mPropertyName
4339                                     + ", areaId: "
4340                                     + areaId;
4341                     try {
4342                         boolean result =
4343                                 mCarPropertyManager.isPropertyAvailable(mPropertyId, areaId);
4344                         assertWithMessage(errorMsg).that(result).isFalse();
4345                     } catch (Exception e) {
4346                         assertWithMessage(errorMsg)
4347                                 .that(e.getClass())
4348                                 .isEqualTo(IllegalArgumentException.class);
4349                     }
4350                     return;
4351                 }
4352             }
4353 
4354             boolean result = false;
4355             try {
4356                 result = mCarPropertyManager.isPropertyAvailable(mPropertyId, areaId);
4357             } catch (Exception e) {
4358                 assertWithMessage(
4359                                 "carPropertyManager.isPropertyAvailable must not throw any"
4360                                         + " exception, propertyId: "
4361                                         + mPropertyName
4362                                         + ", areaId: "
4363                                         + areaId
4364                                         + ", exception: "
4365                                         + e)
4366                         .fail();
4367             }
4368             if (carPropertyConfig == null) {
4369                 assertWithMessage(
4370                                 "carPropertyManager.isPropertyAvailable must return false "
4371                                         + "if the property is not supported, propertyId: "
4372                                         + mPropertyName
4373                                         + ", areaId: "
4374                                         + areaId)
4375                         .that(result)
4376                         .isFalse();
4377             }
4378         }
4379     }
4380 
setPropertyAndWaitForChange( CarPropertyManager carPropertyManager, int propertyId, Class<U> propertyType, int areaId, U valueToSet)4381     private static <U> CarPropertyValue<U> setPropertyAndWaitForChange(
4382             CarPropertyManager carPropertyManager,
4383             int propertyId,
4384             Class<U> propertyType,
4385             int areaId,
4386             U valueToSet) {
4387         return setPropertyAndWaitForChange(
4388                 carPropertyManager, propertyId, propertyType, areaId, valueToSet, valueToSet);
4389     }
4390 
setPropertyAndWaitForChange( CarPropertyManager carPropertyManager, int propertyId, Class<U> propertyType, int areaId, U valueToSet, U expectedValueToGet)4391     private static <U> CarPropertyValue<U> setPropertyAndWaitForChange(
4392             CarPropertyManager carPropertyManager,
4393             int propertyId,
4394             Class<U> propertyType,
4395             int areaId,
4396             U valueToSet,
4397             U expectedValueToGet) {
4398         spaceOutCarPropertyManagerActions();
4399         SetterCallback setterCallback = new SetterCallback(propertyId, areaId, expectedValueToGet);
4400         assertWithMessage(
4401                         "Failed to register setter callback for "
4402                                 + VehiclePropertyIds.toString(propertyId))
4403                 .that(
4404                         subscribePropertyEvents(
4405                                 carPropertyManager,
4406                                 setterCallback,
4407                                 propertyId,
4408                                 CarPropertyManager.SENSOR_RATE_FASTEST))
4409                 .isTrue();
4410         try {
4411             carPropertyManager.setProperty(propertyType, propertyId, areaId, valueToSet);
4412         } catch (PropertyNotAvailableAndRetryException e) {
4413             return null;
4414         } catch (PropertyNotAvailableException e) {
4415             verifyPropertyNotAvailableException(e);
4416             sExceptionClassOnSet = e.getClass();
4417             return null;
4418         } catch (CarInternalErrorException e) {
4419             verifyInternalErrorException(e);
4420             sExceptionClassOnSet = e.getClass();
4421             return null;
4422         }
4423 
4424         CarPropertyValue<U> carPropertyValue =
4425                 setterCallback.waitForPropertyEvent(SET_PROPERTY_CALLBACK_TIMEOUT_SEC);
4426         unsubscribePropertyEvents(carPropertyManager, setterCallback, propertyId);
4427         return carPropertyValue;
4428     }
4429 
spaceOutCarPropertyManagerActions()4430     private static void spaceOutCarPropertyManagerActions() {
4431         synchronized (sLock) {
4432             do {
4433                 long currentElapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos();
4434                 long remainingDelayMs =
4435                         CPM_ACTION_DELAY_MS
4436                                 - Duration.ofNanos(
4437                                                 currentElapsedRealtimeNanos
4438                                                         - sLastActionElapsedRealtimeNanos)
4439                                         .toMillis();
4440                 if (remainingDelayMs <= 0) {
4441                     sLastActionElapsedRealtimeNanos = currentElapsedRealtimeNanos;
4442                     break;
4443                 }
4444                 SystemClock.sleep(remainingDelayMs);
4445             } while (true);
4446         }
4447     }
4448 }
4449