1 /*
2  * Copyright (C) 2019 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 com.android.car;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import static org.junit.Assert.fail;
22 import static org.testng.Assert.assertThrows;
23 
24 import android.car.Car;
25 import android.car.VehicleAreaType;
26 import android.car.VehiclePropertyIds;
27 import android.car.hardware.CarPropertyConfig;
28 import android.car.hardware.CarPropertyValue;
29 import android.car.hardware.property.CarInternalErrorException;
30 import android.car.hardware.property.CarPropertyManager;
31 import android.car.hardware.property.PropertyAccessDeniedSecurityException;
32 import android.car.hardware.property.PropertyNotAvailableAndRetryException;
33 import android.car.hardware.property.PropertyNotAvailableException;
34 import android.car.hardware.property.VehicleHalStatusCode;
35 import android.car.test.util.Visitor;
36 import android.hardware.automotive.vehicle.V2_0.VehicleArea;
37 import android.hardware.automotive.vehicle.V2_0.VehicleAreaSeat;
38 import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
39 import android.hardware.automotive.vehicle.V2_0.VehiclePropertyGroup;
40 import android.hardware.automotive.vehicle.V2_0.VehiclePropertyType;
41 import android.os.Build;
42 import android.os.ServiceSpecificException;
43 import android.os.SystemClock;
44 import android.util.ArraySet;
45 import android.util.Log;
46 
47 import androidx.test.ext.junit.runners.AndroidJUnit4;
48 import androidx.test.filters.MediumTest;
49 
50 import com.android.car.vehiclehal.test.MockedVehicleHal.VehicleHalPropertyHandler;
51 
52 import com.google.common.truth.Truth;
53 
54 import org.junit.Assert;
55 import org.junit.Rule;
56 import org.junit.Test;
57 import org.junit.rules.TestName;
58 import org.junit.runner.RunWith;
59 
60 import java.util.ArrayList;
61 import java.util.Arrays;
62 import java.util.HashMap;
63 import java.util.List;
64 import java.util.concurrent.ConcurrentHashMap;
65 import java.util.concurrent.CountDownLatch;
66 import java.util.concurrent.TimeUnit;
67 
68 /**
69  * Test for {@link android.car.hardware.property.CarPropertyManager}
70  */
71 @RunWith(AndroidJUnit4.class)
72 @MediumTest
73 public class CarPropertyManagerTest extends MockedCarTestBase {
74 
75     private static final String TAG = CarPropertyManagerTest.class.getSimpleName();
76 
77     /**
78      * configArray[0], 1 indicates the property has a String value
79      * configArray[1], 1 indicates the property has a Boolean value .
80      * configArray[2], 1 indicates the property has a Integer value
81      * configArray[3], the number indicates the size of Integer[]  in the property.
82      * configArray[4], 1 indicates the property has a Long value .
83      * configArray[5], the number indicates the size of Long[]  in the property.
84      * configArray[6], 1 indicates the property has a Float value .
85      * configArray[7], the number indicates the size of Float[] in the property.
86      * configArray[8], the number indicates the size of byte[] in the property.
87      */
88     private static final java.util.Collection<Integer> CONFIG_ARRAY_1 =
89             Arrays.asList(1, 0, 1, 0, 1, 0, 0, 0, 0);
90     private static final java.util.Collection<Integer> CONFIG_ARRAY_2 =
91             Arrays.asList(1, 1, 1, 0, 0, 0, 0, 2, 0);
92     private static final Object[] EXPECTED_VALUE_1 = {"android", 1, 1L};
93     private static final Object[] EXPECTED_VALUE_2 = {"android", true, 3, 1.1f, 2f};
94 
95     private static final int CUSTOM_SEAT_INT_PROP_1 =
96             0x1201 | VehiclePropertyGroup.VENDOR | VehiclePropertyType.INT32 | VehicleArea.SEAT;
97     private static final int CUSTOM_SEAT_INT_PROP_2 =
98             0x1202 | VehiclePropertyGroup.VENDOR | VehiclePropertyType.INT32 | VehicleArea.SEAT;
99 
100     private static final int CUSTOM_SEAT_MIXED_PROP_ID_1 =
101             0x1101 | VehiclePropertyGroup.VENDOR | VehiclePropertyType.MIXED | VehicleArea.SEAT;
102     private static final int CUSTOM_GLOBAL_MIXED_PROP_ID_2 =
103             0x1102 | VehiclePropertyGroup.VENDOR | VehiclePropertyType.MIXED | VehicleArea.GLOBAL;
104 
105     private static final int CUSTOM_GLOBAL_INT_ARRAY_PROP =
106             0x1103 | VehiclePropertyGroup.VENDOR | VehiclePropertyType.INT32_VEC
107                     | VehicleArea.GLOBAL;
108     private static final Integer[] FAKE_INT_ARRAY_VALUE = {1, 2};
109 
110     // Vendor properties for testing exceptions.
111     private static final int PROP_CAUSE_STATUS_CODE_TRY_AGAIN =
112             0x1201 | VehiclePropertyGroup.VENDOR | VehiclePropertyType.INT32 | VehicleArea.GLOBAL;
113     private static final int PROP_CAUSE_STATUS_CODE_INVALID_ARG =
114             0x1202 | VehiclePropertyGroup.VENDOR | VehiclePropertyType.INT32 | VehicleArea.GLOBAL;
115     private static final int PROP_CAUSE_STATUS_CODE_NOT_AVAILABLE =
116             0x1203 | VehiclePropertyGroup.VENDOR | VehiclePropertyType.INT32 | VehicleArea.GLOBAL;
117     private static final int PROP_CAUSE_STATUS_CODE_INTERNAL_ERROR =
118             0x1204 | VehiclePropertyGroup.VENDOR | VehiclePropertyType.INT32 | VehicleArea.GLOBAL;
119     private static final int PROP_CAUSE_STATUS_CODE_ACCESS_DENIED =
120             0x1205 | VehiclePropertyGroup.VENDOR | VehiclePropertyType.INT32 | VehicleArea.GLOBAL;
121 
122     // Use FAKE_PROPERTY_ID to test api return null or throw exception.
123     private static final int FAKE_PROPERTY_ID = 0x111;
124 
125     private static final int DRIVER_SIDE_AREA_ID = VehicleAreaSeat.ROW_1_LEFT
126                                                     | VehicleAreaSeat.ROW_2_LEFT;
127     private static final int PASSENGER_SIDE_AREA_ID = VehicleAreaSeat.ROW_1_RIGHT
128                                                     | VehicleAreaSeat.ROW_2_CENTER
129                                                     | VehicleAreaSeat.ROW_2_RIGHT;
130     private static final float INIT_TEMP_VALUE = 16f;
131     private static final float CHANGED_TEMP_VALUE = 20f;
132     private static final int CALLBACK_SHORT_TIMEOUT_MS = 250; // ms
133     // Wait for CarPropertyManager register/unregister listener
134     private static final long WAIT_FOR_NO_EVENTS = 50;
135 
136     private static final List<Integer> USER_HAL_PROPERTIES = Arrays.asList(
137             VehiclePropertyIds.INITIAL_USER_INFO,
138             VehiclePropertyIds.SWITCH_USER,
139             VehiclePropertyIds.CREATE_USER,
140             VehiclePropertyIds.REMOVE_USER,
141             VehiclePropertyIds.USER_IDENTIFICATION_ASSOCIATION
142             );
143 
144     private CarPropertyManager mManager;
145 
146     @Rule public TestName mTestName = new TestName();
147 
148     @Override
setUp()149     public void setUp() throws Exception {
150         super.setUp();
151         setUpTargetSdk();
152         mManager = (CarPropertyManager) getCar().getCarManager(Car.PROPERTY_SERVICE);
153         assertThat(mManager).isNotNull();
154     }
155 
setUpTargetSdk()156     private void setUpTargetSdk() {
157         if (mTestName.getMethodName().endsWith("InQ")) {
158             getContext().getApplicationInfo().targetSdkVersion = Build.VERSION_CODES.Q;
159         } else if (mTestName.getMethodName().endsWith("InR")) {
160             getContext().getApplicationInfo().targetSdkVersion = Build.VERSION_CODES.R;
161         }
162     }
163 
164     @Test
testMixedPropertyConfigs()165     public void testMixedPropertyConfigs() {
166         List<CarPropertyConfig> configs = mManager.getPropertyList();
167         for (CarPropertyConfig cfg : configs) {
168             switch (cfg.getPropertyId()) {
169                 case CUSTOM_SEAT_MIXED_PROP_ID_1:
170                     assertThat(cfg.getConfigArray()).containsExactlyElementsIn(CONFIG_ARRAY_1)
171                             .inOrder();
172                     break;
173                 case CUSTOM_GLOBAL_MIXED_PROP_ID_2:
174                     assertThat(cfg.getConfigArray()).containsExactlyElementsIn(CONFIG_ARRAY_2)
175                             .inOrder();
176                     break;
177                 case VehiclePropertyIds.HVAC_TEMPERATURE_SET:
178                 case PROP_CAUSE_STATUS_CODE_ACCESS_DENIED:
179                 case PROP_CAUSE_STATUS_CODE_INTERNAL_ERROR:
180                 case PROP_CAUSE_STATUS_CODE_TRY_AGAIN:
181                 case PROP_CAUSE_STATUS_CODE_NOT_AVAILABLE:
182                 case PROP_CAUSE_STATUS_CODE_INVALID_ARG:
183                 case CUSTOM_SEAT_INT_PROP_1:
184                 case CUSTOM_SEAT_INT_PROP_2:
185                 case CUSTOM_GLOBAL_INT_ARRAY_PROP:
186                 case VehiclePropertyIds.INFO_VIN:
187                     break;
188                 default:
189                     Assert.fail("Unexpected CarPropertyConfig: " + cfg.toString());
190             }
191         }
192     }
193 
194     @Test
testGetMixTypeProperty()195     public void testGetMixTypeProperty() {
196         mManager.setProperty(Object[].class, CUSTOM_SEAT_MIXED_PROP_ID_1,
197                 0, EXPECTED_VALUE_1);
198         CarPropertyValue<Object[]> result = mManager.getProperty(
199                 CUSTOM_SEAT_MIXED_PROP_ID_1, 0);
200         assertThat(result.getValue()).isEqualTo(EXPECTED_VALUE_1);
201         mManager.setProperty(Object[].class, CUSTOM_GLOBAL_MIXED_PROP_ID_2,
202                 0, EXPECTED_VALUE_2);
203         result = mManager.getProperty(
204                 CUSTOM_GLOBAL_MIXED_PROP_ID_2, 0);
205         assertThat(result.getValue()).isEqualTo(EXPECTED_VALUE_2);
206     }
207 
208     /**
209      * Test {@link android.car.hardware.property.CarPropertyManager#getIntArrayProperty(int, int)}
210      */
211     @Test
testGetIntArrayProperty()212     public void testGetIntArrayProperty() {
213         mManager.setProperty(Integer[].class, CUSTOM_GLOBAL_INT_ARRAY_PROP, VehicleArea.GLOBAL,
214                 FAKE_INT_ARRAY_VALUE);
215 
216         int[] result = mManager.getIntArrayProperty(CUSTOM_GLOBAL_INT_ARRAY_PROP,
217                 VehicleArea.GLOBAL);
218         assertThat(result).asList().containsExactlyElementsIn(FAKE_INT_ARRAY_VALUE);
219     }
220 
221     /**
222      * Test {@link CarPropertyManager#getProperty(Class, int, int)}
223      */
224     @Test
testGetPropertyWithClass()225     public void testGetPropertyWithClass() {
226         mManager.setProperty(Integer[].class, CUSTOM_GLOBAL_INT_ARRAY_PROP, VehicleArea.GLOBAL,
227                 FAKE_INT_ARRAY_VALUE);
228 
229         CarPropertyValue<Integer[]> result = mManager.getProperty(Integer[].class,
230                 CUSTOM_GLOBAL_INT_ARRAY_PROP, VehicleArea.GLOBAL);
231         assertThat(result.getValue()).asList().containsExactlyElementsIn(FAKE_INT_ARRAY_VALUE);
232     }
233 
234     /**
235      * Test {@link CarPropertyManager#isPropertyAvailable(int, int)}
236      */
237     @Test
testIsPropertyAvailable()238     public void testIsPropertyAvailable() {
239         assertThat(mManager.isPropertyAvailable(FAKE_PROPERTY_ID, VehicleArea.GLOBAL)).isFalse();
240         assertThat(mManager.isPropertyAvailable(CUSTOM_GLOBAL_INT_ARRAY_PROP, VehicleArea.GLOBAL))
241                 .isTrue();
242     }
243 
244     /**
245      * Test {@link CarPropertyManager#getWritePermission(int)}
246      * and {@link CarPropertyManager#getWritePermission(int)}
247      */
248     @Test
testGetPermission()249     public void testGetPermission() {
250         String hvacReadPermission = mManager.getReadPermission(
251                 VehiclePropertyIds.HVAC_TEMPERATURE_SET);
252         assertThat(hvacReadPermission).isEqualTo(Car.PERMISSION_CONTROL_CAR_CLIMATE);
253         String hvacWritePermission = mManager.getWritePermission(
254                 VehiclePropertyIds.HVAC_TEMPERATURE_SET);
255         assertThat(hvacWritePermission).isEqualTo(Car.PERMISSION_CONTROL_CAR_CLIMATE);
256 
257         // For read-only property
258         String vinReadPermission = mManager.getReadPermission(VehiclePropertyIds.INFO_VIN);
259         assertThat(vinReadPermission).isEqualTo(Car.PERMISSION_IDENTIFICATION);
260         String vinWritePermission = mManager.getWritePermission(VehiclePropertyIds.INFO_VIN);
261         assertThat(vinWritePermission).isNull();
262     }
263 
264     @Test
testGetPropertyConfig()265     public void testGetPropertyConfig() {
266         CarPropertyConfig config = mManager.getCarPropertyConfig(CUSTOM_SEAT_MIXED_PROP_ID_1);
267         assertThat(config.getPropertyId()).isEqualTo(CUSTOM_SEAT_MIXED_PROP_ID_1);
268         // return null if can not find the propertyConfig for the property.
269         assertThat(mManager.getCarPropertyConfig(FAKE_PROPERTY_ID)).isNull();
270     }
271 
272     @Test
testGetAreaId()273     public void testGetAreaId() {
274         int result = mManager.getAreaId(CUSTOM_SEAT_MIXED_PROP_ID_1, VehicleAreaSeat.ROW_1_LEFT);
275         assertThat(result).isEqualTo(DRIVER_SIDE_AREA_ID);
276         //test for the GLOBAL property
277         int globalAreaId =
278                 mManager.getAreaId(CUSTOM_GLOBAL_MIXED_PROP_ID_2, VehicleAreaSeat.ROW_1_LEFT);
279         assertThat(globalAreaId).isEqualTo(VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL);
280         //test exception
281         assertThrows(IllegalArgumentException.class, () -> mManager.getAreaId(
282                 CUSTOM_SEAT_MIXED_PROP_ID_1, VehicleAreaSeat.ROW_3_CENTER));
283         assertThrows(IllegalArgumentException.class, () -> mManager.getAreaId(FAKE_PROPERTY_ID,
284                 VehicleAreaSeat.ROW_1_LEFT));
285     }
286 
287     @Test
testNotReceiveOnErrorEvent()288     public void testNotReceiveOnErrorEvent() throws Exception {
289         TestErrorCallback callback = new TestErrorCallback();
290         mManager.registerCallback(callback, VehiclePropertyIds.HVAC_TEMPERATURE_SET,
291                 CarPropertyManager.SENSOR_RATE_ONCHANGE);
292         callback.assertRegisterCompleted();
293         injectErrorEvent(VehiclePropertyIds.HVAC_TEMPERATURE_SET, PASSENGER_SIDE_AREA_ID,
294                 CarPropertyManager.CAR_SET_PROPERTY_ERROR_CODE_UNKNOWN);
295         // app never change the value of HVAC_TEMPERATURE_SET, it won't get an error code.
296         callback.assertOnErrorEventNotCalled();
297     }
298 
299     @Test
testReceiveOnErrorEvent()300     public void testReceiveOnErrorEvent() throws Exception {
301         TestErrorCallback callback = new TestErrorCallback();
302         mManager.registerCallback(callback, VehiclePropertyIds.HVAC_TEMPERATURE_SET,
303                 CarPropertyManager.SENSOR_RATE_ONCHANGE);
304         callback.assertRegisterCompleted();
305         mManager.setFloatProperty(
306                 VehiclePropertyIds.HVAC_TEMPERATURE_SET, PASSENGER_SIDE_AREA_ID,
307                 CHANGED_TEMP_VALUE);
308         injectErrorEvent(VehiclePropertyIds.HVAC_TEMPERATURE_SET, PASSENGER_SIDE_AREA_ID,
309                 CarPropertyManager.CAR_SET_PROPERTY_ERROR_CODE_UNKNOWN);
310         callback.assertOnErrorEventCalled();
311         assertThat(callback.mReceivedErrorEventWithErrorCode).isTrue();
312         assertThat(callback.mErrorCode).isEqualTo(
313                 CarPropertyManager.CAR_SET_PROPERTY_ERROR_CODE_UNKNOWN);
314         assertThat(callback.mReceivedErrorEventWithOutErrorCode).isFalse();
315     }
316 
317     @Test
testNotReceiveOnErrorEventAfterUnregister()318     public void testNotReceiveOnErrorEventAfterUnregister() throws Exception {
319         TestErrorCallback callback1 = new TestErrorCallback();
320         mManager.registerCallback(callback1, VehiclePropertyIds.HVAC_TEMPERATURE_SET,
321                 CarPropertyManager.SENSOR_RATE_ONCHANGE);
322         callback1.assertRegisterCompleted();
323         TestErrorCallback callback2 = new TestErrorCallback();
324         mManager.registerCallback(callback2, VehiclePropertyIds.HVAC_TEMPERATURE_SET,
325                 CarPropertyManager.SENSOR_RATE_ONCHANGE);
326         mManager.setFloatProperty(
327                 VehiclePropertyIds.HVAC_TEMPERATURE_SET, PASSENGER_SIDE_AREA_ID,
328                 CHANGED_TEMP_VALUE);
329         mManager.unregisterCallback(callback1, VehiclePropertyIds.HVAC_TEMPERATURE_SET);
330         SystemClock.sleep(WAIT_FOR_NO_EVENTS);
331         injectErrorEvent(VehiclePropertyIds.HVAC_TEMPERATURE_SET, PASSENGER_SIDE_AREA_ID,
332                 CarPropertyManager.CAR_SET_PROPERTY_ERROR_CODE_UNKNOWN);
333         // callback1 is unregistered
334         callback1.assertOnErrorEventNotCalled();
335         callback2.assertOnErrorEventCalled();
336     }
337     @Test
testSetterExceptionsInQ()338     public void testSetterExceptionsInQ() {
339         Truth.assertThat(getContext().getApplicationInfo().targetSdkVersion)
340                 .isEqualTo(Build.VERSION_CODES.Q);
341 
342         assertThrows(IllegalStateException.class,
343                 ()->mManager.setProperty(Integer.class, PROP_CAUSE_STATUS_CODE_ACCESS_DENIED,
344                         VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL, 1));
345         assertThrows(IllegalStateException.class,
346                 ()->mManager.setProperty(Integer.class, PROP_CAUSE_STATUS_CODE_INTERNAL_ERROR,
347                         VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL, 1));
348         assertThrows(IllegalStateException.class,
349                 ()->mManager.setProperty(Integer.class, PROP_CAUSE_STATUS_CODE_NOT_AVAILABLE,
350                         VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL, 1));
351         assertThrows(IllegalArgumentException.class,
352                 ()->mManager.setProperty(Integer.class, PROP_CAUSE_STATUS_CODE_INVALID_ARG,
353                         VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL, 1));
354         assertThrows(RuntimeException.class,
355                 ()->mManager.setProperty(Integer.class, PROP_CAUSE_STATUS_CODE_ACCESS_DENIED,
356                         VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL, 1));
357     }
358 
359     @Test
testSetterExceptionsInR()360     public void testSetterExceptionsInR() {
361         Truth.assertThat(getContext().getApplicationInfo().targetSdkVersion)
362                 .isEqualTo(Build.VERSION_CODES.R);
363 
364         assertThrows(PropertyAccessDeniedSecurityException.class,
365                 ()->mManager.setProperty(Integer.class, PROP_CAUSE_STATUS_CODE_ACCESS_DENIED,
366                         VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL, 1));
367         assertThrows(PropertyNotAvailableAndRetryException.class,
368                 ()->mManager.setProperty(Integer.class, PROP_CAUSE_STATUS_CODE_TRY_AGAIN,
369                         VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL, 1));
370         assertThrows(PropertyNotAvailableException.class,
371                 ()->mManager.setProperty(Integer.class, PROP_CAUSE_STATUS_CODE_NOT_AVAILABLE,
372                         VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL, 1));
373         assertThrows(CarInternalErrorException.class,
374                 ()->mManager.setProperty(Integer.class, PROP_CAUSE_STATUS_CODE_INTERNAL_ERROR,
375                         VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL, 1));
376         assertThrows(IllegalArgumentException.class,
377                 ()->mManager.setProperty(Integer.class, PROP_CAUSE_STATUS_CODE_INVALID_ARG,
378                         VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL, 1));
379     }
380 
381     @Test
testGetterExceptionsInQ()382     public void testGetterExceptionsInQ() {
383         Truth.assertThat(getContext().getApplicationInfo().targetSdkVersion)
384                 .isEqualTo(Build.VERSION_CODES.Q);
385 
386         assertThrows(IllegalStateException.class,
387                 ()->mManager.getProperty(PROP_CAUSE_STATUS_CODE_ACCESS_DENIED,
388                         VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL));
389         assertThrows(IllegalArgumentException.class,
390                 ()->mManager.getProperty(PROP_CAUSE_STATUS_CODE_INVALID_ARG,
391                         VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL));
392         assertThrows(IllegalStateException.class,
393                 ()->mManager.getProperty(PROP_CAUSE_STATUS_CODE_NOT_AVAILABLE,
394                         VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL));
395         assertThrows(IllegalStateException.class,
396                 ()->mManager.getProperty(PROP_CAUSE_STATUS_CODE_INTERNAL_ERROR,
397                         VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL));
398         Truth.assertThat(mManager.getProperty(PROP_CAUSE_STATUS_CODE_TRY_AGAIN,
399                 VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL)).isNull();
400     }
401 
402     @Test
testGetterExceptionsInR()403     public void testGetterExceptionsInR() {
404         Truth.assertThat(getContext().getApplicationInfo().targetSdkVersion)
405                 .isEqualTo(Build.VERSION_CODES.R);
406 
407         assertThrows(PropertyAccessDeniedSecurityException.class,
408                 () -> mManager.getProperty(PROP_CAUSE_STATUS_CODE_ACCESS_DENIED,
409                         VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL));
410         assertThrows(IllegalArgumentException.class,
411                 () -> mManager.getProperty(PROP_CAUSE_STATUS_CODE_INVALID_ARG,
412                         VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL));
413         assertThrows(PropertyNotAvailableAndRetryException.class,
414                 () -> mManager.getProperty(PROP_CAUSE_STATUS_CODE_TRY_AGAIN,
415                         VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL));
416         assertThrows(PropertyNotAvailableException.class,
417                 () -> mManager.getProperty(PROP_CAUSE_STATUS_CODE_NOT_AVAILABLE,
418                         VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL));
419         assertThrows(CarInternalErrorException.class,
420                 () -> mManager.getProperty(PROP_CAUSE_STATUS_CODE_INTERNAL_ERROR,
421                         VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL));
422     }
423 
424     @Test
testOnChangeEventWithSameAreaId()425     public void testOnChangeEventWithSameAreaId() throws Exception {
426         // init
427         mManager.setProperty(Integer.class,
428                 CUSTOM_SEAT_INT_PROP_1, DRIVER_SIDE_AREA_ID, 1);
429         TestSequenceCallback callback = new TestSequenceCallback(1);
430         mManager.registerCallback(callback, CUSTOM_SEAT_INT_PROP_1, 0);
431         callback.assertRegisterCompleted();
432 
433         VehiclePropValue firstFakeValueDriveSide = new VehiclePropValue();
434         firstFakeValueDriveSide.prop = CUSTOM_SEAT_INT_PROP_1;
435         firstFakeValueDriveSide.areaId = DRIVER_SIDE_AREA_ID;
436         firstFakeValueDriveSide.value.int32Values.add(2);
437         firstFakeValueDriveSide.timestamp = SystemClock.elapsedRealtimeNanos();
438         VehiclePropValue secFakeValueDriveSide = new VehiclePropValue();
439         secFakeValueDriveSide.prop = CUSTOM_SEAT_INT_PROP_1;
440         secFakeValueDriveSide.areaId = DRIVER_SIDE_AREA_ID;
441         secFakeValueDriveSide.value.int32Values.add(3); // 0 in HAL indicate false;
442         secFakeValueDriveSide.timestamp = SystemClock.elapsedRealtimeNanos();
443         // inject the new event first
444         getMockedVehicleHal().injectEvent(secFakeValueDriveSide);
445         // inject the old event
446         getMockedVehicleHal().injectEvent(firstFakeValueDriveSide);
447         callback.assertOnChangeEventCalled();
448         // Client should only get the new event
449         assertThat((int) callback.getLastCarPropertyValue(CUSTOM_SEAT_INT_PROP_1).getValue())
450                 .isEqualTo(3);
451         assertThat(callback.getEventCounter()).isEqualTo(1);
452 
453     }
454 
455     @Test
testOnChangeEventWithDifferentAreaId()456     public void testOnChangeEventWithDifferentAreaId() throws Exception {
457         // init
458         mManager.setProperty(Integer.class,
459                 CUSTOM_SEAT_INT_PROP_2, DRIVER_SIDE_AREA_ID, 1);
460         TestSequenceCallback callback = new TestSequenceCallback(2);
461         mManager.registerCallback(callback, CUSTOM_SEAT_INT_PROP_2, 0);
462         callback.assertRegisterCompleted();
463         VehiclePropValue fakeValueDriveSide = new VehiclePropValue();
464         fakeValueDriveSide.prop = CUSTOM_SEAT_INT_PROP_2;
465         fakeValueDriveSide.areaId = DRIVER_SIDE_AREA_ID;
466         fakeValueDriveSide.value.int32Values.add(4);
467         fakeValueDriveSide.timestamp = SystemClock.elapsedRealtimeNanos();
468 
469         VehiclePropValue fakeValuePsgSide = new VehiclePropValue();
470         fakeValuePsgSide.prop = CUSTOM_SEAT_INT_PROP_2;
471         fakeValuePsgSide.areaId = PASSENGER_SIDE_AREA_ID;
472         fakeValuePsgSide.value.int32Values.add(5);
473         fakeValuePsgSide.timestamp = SystemClock.elapsedRealtimeNanos();
474 
475         // inject passenger event before driver event
476         getMockedVehicleHal().injectEvent(fakeValuePsgSide);
477         getMockedVehicleHal().injectEvent(fakeValueDriveSide);
478         callback.assertOnChangeEventCalled();
479 
480         // both events should be received by listener
481         assertThat((int) callback.getLastCarPropertyValue(CUSTOM_SEAT_INT_PROP_2).getValue())
482                 .isEqualTo(4);
483         assertThat(callback.getEventCounter()).isEqualTo(2);
484     }
485 
486     @Test
testUserHal_getProperty()487     public void testUserHal_getProperty() {
488         userHalPropertiesTest("getProperty()", (prop) ->
489                 mManager.getProperty(prop, /* areaId= */ 0));
490     }
491 
492     @Test
testUserHal_getBooleanProperty()493     public void testUserHal_getBooleanProperty() {
494         userHalPropertiesTest("getBooleanProperty()", (prop) ->
495                 mManager.getBooleanProperty(prop, /* areaId= */ 0));
496     }
497 
498     @Test
testUserHal_getIntProperty()499     public void testUserHal_getIntProperty() {
500         userHalPropertiesTest("getIntProperty()", (prop) ->
501                 mManager.getIntProperty(prop, /* areaId= */ 0));
502     }
503 
504     @Test
testUserHal_getIntArrayProperty()505     public void testUserHal_getIntArrayProperty() {
506         userHalPropertiesTest("getIntArrayProperty()", (prop) ->
507                 mManager.getIntArrayProperty(prop, /* areaId= */ 0));
508     }
509 
510     @Test
testUserHal_getFloatProperty()511     public void testUserHal_getFloatProperty() {
512         userHalPropertiesTest("getFloatProperty()", (prop) ->
513                 mManager.getFloatProperty(prop, /* areaId= */ 0));
514     }
515 
516     @Test
testUserHal_getPropertyList()517     public void testUserHal_getPropertyList() {
518         userHalPropertiesTest("getPropertyList()", (prop) -> {
519             ArraySet<Integer> list = new ArraySet<>();
520             list.add(prop);
521             mManager.getPropertyList(list);
522         });
523     }
524 
525     @Test
testUserHal_getCarPropertyConfig()526     public void testUserHal_getCarPropertyConfig() {
527         userHalPropertiesTest("getCarPropertyConfig()", (prop) ->
528                 mManager.getCarPropertyConfig(prop));
529     }
530 
531     @Test
testUserHal_getAreaId()532     public void testUserHal_getAreaId() {
533         userHalPropertiesTest("getAreaId()", (prop) ->
534                 mManager.getAreaId(prop, /* areaId= */ 0));
535     }
536 
537     @Test
testUserHal_getReadPermission()538     public void testUserHal_getReadPermission() {
539         userHalPropertiesTest("getReadPermission()", (prop) ->
540                 mManager.getReadPermission(prop));
541     }
542 
543     @Test
testUserHal_getWritePermission()544     public void testUserHal_getWritePermission() {
545         userHalPropertiesTest("getWritePermission()", (prop) ->
546                 mManager.getWritePermission(prop));
547     }
548 
549     @Test
testUserHal_isPropertyAvailable()550     public void testUserHal_isPropertyAvailable() {
551         userHalPropertiesTest("isPropertyAvailable()", (prop) ->
552                 mManager.isPropertyAvailable(prop, /* area= */ 0));
553     }
554 
555     @Test
testUserHal_setProperty()556     public void testUserHal_setProperty() {
557         userHalPropertiesTest("setProperty()", (prop) ->
558                 mManager.setProperty(Object.class, prop, /* areaId= */ 0, /* val= */ null));
559     }
560 
561     @Test
testUserHal_setBooleanProperty()562     public void testUserHal_setBooleanProperty() {
563         userHalPropertiesTest("setBooleanProperty()", (prop) ->
564                 mManager.setBooleanProperty(prop, /* areaId= */ 0, /* val= */ true));
565     }
566 
567     @Test
testUserHal_setFloatProperty()568     public void testUserHal_setFloatProperty() {
569         userHalPropertiesTest("setFloatProperty()", (prop) ->
570                 mManager.setFloatProperty(prop, /* areaId= */ 0, /* val= */ 0.0F));
571     }
572 
573     @Test
testUserHal_setIntProperty()574     public void testUserHal_setIntProperty() {
575         userHalPropertiesTest("setIntProperty()", (prop) ->
576                 mManager.setIntProperty(prop, /* areaId= */ 0, /* val= */ 0));
577     }
578 
userHalPropertiesTest(String method, Visitor<Integer> visitor)579     private void userHalPropertiesTest(String method, Visitor<Integer> visitor) {
580         List<String> failedProperties = new ArrayList<String>();
581         for (int propertyId : USER_HAL_PROPERTIES) {
582             try {
583                 visitor.visit(propertyId);
584                 failedProperties.add(propToString(propertyId));
585             } catch (IllegalArgumentException e) {
586                 // expected
587             }
588         }
589         if (!failedProperties.isEmpty()) {
590             fail(method + " should not support these properties: " + failedProperties);
591         }
592     }
593 
594     @Override
configureMockedHal()595     protected synchronized void configureMockedHal() {
596         PropertyHandler handler = new PropertyHandler();
597         addProperty(CUSTOM_SEAT_MIXED_PROP_ID_1, handler).setConfigArray(CONFIG_ARRAY_1)
598                 .addAreaConfig(DRIVER_SIDE_AREA_ID).addAreaConfig(PASSENGER_SIDE_AREA_ID);
599         addProperty(CUSTOM_GLOBAL_MIXED_PROP_ID_2, handler).setConfigArray(CONFIG_ARRAY_2);
600         addProperty(CUSTOM_GLOBAL_INT_ARRAY_PROP, handler);
601 
602         VehiclePropValue tempValue = new VehiclePropValue();
603         tempValue.value.floatValues.add(INIT_TEMP_VALUE);
604         tempValue.prop = VehiclePropertyIds.HVAC_TEMPERATURE_SET;
605         addProperty(VehiclePropertyIds.HVAC_TEMPERATURE_SET, tempValue)
606                 .addAreaConfig(DRIVER_SIDE_AREA_ID).addAreaConfig(PASSENGER_SIDE_AREA_ID);
607         addProperty(VehiclePropertyIds.INFO_VIN);
608 
609         addProperty(PROP_CAUSE_STATUS_CODE_ACCESS_DENIED, handler);
610         addProperty(PROP_CAUSE_STATUS_CODE_TRY_AGAIN, handler);
611         addProperty(PROP_CAUSE_STATUS_CODE_INTERNAL_ERROR, handler);
612         addProperty(PROP_CAUSE_STATUS_CODE_INVALID_ARG, handler);
613         addProperty(PROP_CAUSE_STATUS_CODE_NOT_AVAILABLE, handler);
614 
615         addProperty(CUSTOM_SEAT_INT_PROP_1, handler).addAreaConfig(DRIVER_SIDE_AREA_ID)
616                                                         .addAreaConfig(PASSENGER_SIDE_AREA_ID);
617         addProperty(CUSTOM_SEAT_INT_PROP_2, handler).addAreaConfig(DRIVER_SIDE_AREA_ID)
618                                                         .addAreaConfig(PASSENGER_SIDE_AREA_ID);
619     }
620 
621     private class PropertyHandler implements VehicleHalPropertyHandler {
622         HashMap<Integer, VehiclePropValue> mMap = new HashMap<>();
623         @Override
onPropertySet(VehiclePropValue value)624         public synchronized void onPropertySet(VehiclePropValue value) {
625             // Simulate HalClient.set() behavior.
626             int statusCode = mapPropertyToStatusCode(value.prop);
627             if (statusCode == VehicleHalStatusCode.STATUS_INVALID_ARG) {
628                 throw new IllegalArgumentException();
629             }
630 
631             if (statusCode != VehicleHalStatusCode.STATUS_OK) {
632                 throw new ServiceSpecificException(statusCode);
633             }
634 
635             mMap.put(value.prop, value);
636         }
637 
638         @Override
onPropertyGet(VehiclePropValue value)639         public synchronized VehiclePropValue onPropertyGet(VehiclePropValue value) {
640             // Simulate HalClient.get() behavior.
641             int statusCode = mapPropertyToStatusCode(value.prop);
642             if (statusCode == VehicleHalStatusCode.STATUS_INVALID_ARG) {
643                 throw new IllegalArgumentException();
644             }
645 
646             if (statusCode != VehicleHalStatusCode.STATUS_OK) {
647                 throw new ServiceSpecificException(statusCode);
648             }
649 
650             VehiclePropValue currentValue = mMap.get(value.prop);
651             return currentValue != null ? currentValue : value;
652         }
653 
654         @Override
onPropertySubscribe(int property, float sampleRate)655         public synchronized void onPropertySubscribe(int property, float sampleRate) {
656             Log.d(TAG, "onPropertySubscribe property "
657                     + property + " sampleRate " + sampleRate);
658         }
659 
660         @Override
onPropertyUnsubscribe(int property)661         public synchronized void onPropertyUnsubscribe(int property) {
662             Log.d(TAG, "onPropertyUnSubscribe property " + property);
663         }
664     }
665 
propToString(int propertyId)666     private static String propToString(int propertyId) {
667         return VehiclePropertyIds.toString(propertyId) + " (" + propertyId + ")";
668     }
669 
mapPropertyToStatusCode(int propId)670     private static int mapPropertyToStatusCode(int propId) {
671         switch (propId) {
672             case PROP_CAUSE_STATUS_CODE_TRY_AGAIN:
673                 return VehicleHalStatusCode.STATUS_TRY_AGAIN;
674             case PROP_CAUSE_STATUS_CODE_NOT_AVAILABLE:
675                 return VehicleHalStatusCode.STATUS_NOT_AVAILABLE;
676             case PROP_CAUSE_STATUS_CODE_ACCESS_DENIED:
677                 return VehicleHalStatusCode.STATUS_ACCESS_DENIED;
678             case PROP_CAUSE_STATUS_CODE_INVALID_ARG:
679                 return VehicleHalStatusCode.STATUS_INVALID_ARG;
680             case PROP_CAUSE_STATUS_CODE_INTERNAL_ERROR:
681                 return VehicleHalStatusCode.STATUS_INTERNAL_ERROR;
682             default:
683                 return VehicleHalStatusCode.STATUS_OK;
684         }
685     }
686 
687     private static class TestErrorCallback implements CarPropertyManager.CarPropertyEventCallback {
688 
689         private static final String CALLBACK_TAG = "ErrorEventTest";
690         private boolean mReceivedErrorEventWithErrorCode = false;
691         private boolean mReceivedErrorEventWithOutErrorCode = false;
692         private int mErrorCode;
693         private final CountDownLatch mEventsCountDownLatch = new CountDownLatch(1);
694         private final CountDownLatch mRegisterCountDownLatch = new CountDownLatch(2);
695         @Override
onChangeEvent(CarPropertyValue value)696         public void onChangeEvent(CarPropertyValue value) {
697             Log.d(CALLBACK_TAG, "onChangeEvent: " + value);
698             mRegisterCountDownLatch.countDown();
699         }
700 
701         @Override
onErrorEvent(int propId, int zone)702         public void onErrorEvent(int propId, int zone) {
703             mReceivedErrorEventWithOutErrorCode = true;
704             Log.d(CALLBACK_TAG, "onErrorEvent, propId: " + propId + " zone: " + zone);
705             mEventsCountDownLatch.countDown();
706         }
707 
708         @Override
onErrorEvent(int propId, int areaId, int errorCode)709         public void onErrorEvent(int propId, int areaId, int errorCode) {
710             mReceivedErrorEventWithErrorCode = true;
711             mErrorCode = errorCode;
712             Log.d(CALLBACK_TAG, "onErrorEvent, propId: " + propId + " areaId: " + areaId
713                     + "errorCode: " + errorCode);
714             mEventsCountDownLatch.countDown();
715         }
716 
assertOnErrorEventCalled()717         public void assertOnErrorEventCalled() throws InterruptedException {
718             if (!mEventsCountDownLatch.await(CALLBACK_SHORT_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
719                 throw new IllegalStateException("Callback is not called in "
720                         + CALLBACK_SHORT_TIMEOUT_MS + " ms.");
721             }
722         }
723 
assertOnErrorEventNotCalled()724         public void assertOnErrorEventNotCalled() throws InterruptedException {
725             if (mEventsCountDownLatch.await(CALLBACK_SHORT_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
726                 throw new IllegalStateException("Callback is called in " + CALLBACK_SHORT_TIMEOUT_MS
727                         + " ms.");
728             }
729         }
730 
assertRegisterCompleted()731         public void assertRegisterCompleted() throws InterruptedException {
732             if (!mRegisterCountDownLatch.await(CALLBACK_SHORT_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
733                 throw new IllegalStateException("Register failed in " + CALLBACK_SHORT_TIMEOUT_MS
734                         + " ms.");
735             }
736         }
737     }
738 
739     private class TestSequenceCallback implements CarPropertyManager.CarPropertyEventCallback {
740 
741         private ConcurrentHashMap<Integer, CarPropertyValue> mRecorder = new ConcurrentHashMap<>();
742         private int mCounter = 0;
743         private final CountDownLatch mEventsCountDownLatch;
744         private final CountDownLatch mRegisterCountDownLatch = new CountDownLatch(2);
745         @Override
onChangeEvent(CarPropertyValue value)746         public void onChangeEvent(CarPropertyValue value) {
747             Log.e(TAG, "onChanged get a event " + value);
748             mRecorder.put(value.getPropertyId(), value);
749             mRegisterCountDownLatch.countDown();
750             // Skip initial events
751             if (value.getTimestamp() != 0) {
752                 mCounter++;
753                 mEventsCountDownLatch.countDown();
754             }
755         }
756 
TestSequenceCallback(int expectedTimes)757         TestSequenceCallback(int expectedTimes) {
758             mEventsCountDownLatch = new CountDownLatch(expectedTimes);
759         }
760 
761         @Override
onErrorEvent(int properId, int zone)762         public void onErrorEvent(int properId, int zone) {
763             Log.e(TAG, "TestSequenceCallback get an onErrorEvent");
764         }
765 
getLastCarPropertyValue(int propId)766         public CarPropertyValue getLastCarPropertyValue(int propId) {
767             return mRecorder.get(propId);
768         }
769 
getEventCounter()770         public int getEventCounter() {
771             return mCounter;
772         }
773 
assertOnChangeEventCalled()774         public void assertOnChangeEventCalled() throws InterruptedException {
775             if (!mEventsCountDownLatch.await(CALLBACK_SHORT_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
776                 throw new IllegalStateException("Callback is not called in "
777                         + CALLBACK_SHORT_TIMEOUT_MS + " ms.");
778             }
779         }
780 
assertRegisterCompleted()781         public void assertRegisterCompleted() throws InterruptedException {
782             if (!mRegisterCountDownLatch.await(CALLBACK_SHORT_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
783                 throw new IllegalStateException("Register failed in " + CALLBACK_SHORT_TIMEOUT_MS
784                         + " ms.");
785             }
786         }
787     }
788 
789 }
790