1 /*
2  * Copyright (C) 2022 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 android.car.hardware.property.CarPropertyManager.SENSOR_RATE_ONCHANGE;
20 
21 import static com.android.car.internal.property.CarPropertyHelper.SYNC_OP_LIMIT_TRY_AGAIN;
22 
23 import static com.google.common.truth.Truth.assertThat;
24 import static com.google.common.truth.Truth.assertWithMessage;
25 
26 import static org.junit.Assert.assertThrows;
27 import static org.mockito.ArgumentMatchers.any;
28 import static org.mockito.ArgumentMatchers.anyFloat;
29 import static org.mockito.ArgumentMatchers.anyInt;
30 import static org.mockito.ArgumentMatchers.anyLong;
31 import static org.mockito.ArgumentMatchers.eq;
32 import static org.mockito.Mockito.clearInvocations;
33 import static org.mockito.Mockito.doAnswer;
34 import static org.mockito.Mockito.doNothing;
35 import static org.mockito.Mockito.doThrow;
36 import static org.mockito.Mockito.mock;
37 import static org.mockito.Mockito.never;
38 import static org.mockito.Mockito.timeout;
39 import static org.mockito.Mockito.verify;
40 import static org.mockito.Mockito.when;
41 
42 import android.car.VehicleAreaType;
43 import android.car.VehicleAreaWindow;
44 import android.car.VehiclePropertyIds;
45 import android.car.feature.FeatureFlags;
46 import android.car.hardware.CarPropertyConfig;
47 import android.car.hardware.CarPropertyValue;
48 import android.car.hardware.property.AreaIdConfig;
49 import android.car.hardware.property.CarPropertyEvent;
50 import android.car.hardware.property.CarPropertyManager;
51 import android.car.hardware.property.ICarPropertyEventListener;
52 import android.content.Context;
53 import android.os.IBinder;
54 import android.os.RemoteException;
55 import android.os.ServiceSpecificException;
56 import android.platform.test.ravenwood.RavenwoodRule;
57 import android.util.Log;
58 import android.util.SparseArray;
59 
60 import com.android.car.hal.PropertyHalService;
61 import com.android.car.internal.property.AsyncPropertyServiceRequest;
62 import com.android.car.internal.property.AsyncPropertyServiceRequestList;
63 import com.android.car.internal.property.CarSubscription;
64 import com.android.car.internal.property.IAsyncPropertyResultCallback;
65 import com.android.car.logging.HistogramFactoryInterface;
66 import com.android.modules.expresslog.Histogram;
67 
68 import org.junit.Before;
69 import org.junit.Rule;
70 import org.junit.Test;
71 import org.mockito.ArgumentCaptor;
72 import org.mockito.Captor;
73 import org.mockito.Mock;
74 import org.mockito.MockitoAnnotations;
75 
76 import java.time.Duration;
77 import java.util.List;
78 import java.util.concurrent.CountDownLatch;
79 import java.util.concurrent.Executor;
80 import java.util.concurrent.Executors;
81 import java.util.concurrent.TimeUnit;
82 
83 public final class CarPropertyServiceUnitTest {
84     private static final String TAG = CarLog.tagFor(CarPropertyServiceUnitTest.class);
85 
86     @Rule
87     public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
88             .setProcessSystem()
89             .setProvideMainThread(true)
90             .build();
91 
92     @Mock
93     private Context mContext;
94     @Mock
95     private PropertyHalService mHalService;
96     @Mock
97     private ICarPropertyEventListener mICarPropertyEventListener;
98     @Mock
99     private IBinder mIBinder;
100     @Mock
101     private IAsyncPropertyResultCallback mAsyncPropertyResultCallback;
102     @Mock
103     private FeatureFlags mFeatureFlags;
104     @Mock
105     private HistogramFactoryInterface mHistogramFactory;
106     @Captor
107     private ArgumentCaptor<List<CarPropertyEvent>> mPropertyEventCaptor;
108 
109     private CarPropertyService mService;
110 
111     private final SparseArray<CarPropertyConfig<?>> mConfigs = new SparseArray<>();
112 
113     private static final int SPEED_ID = VehiclePropertyIds.PERF_VEHICLE_SPEED;
114     private static final int HVAC_TEMP = VehiclePropertyIds.HVAC_TEMPERATURE_SET;
115     private static final int CONTINUOUS_READ_ONLY_PROPERTY_ID = 98732;
116     private static final int WRITE_ONLY_INT_PROPERTY_ID = 12345;
117     private static final int WRITE_ONLY_LONG_PROPERTY_ID = 22345;
118     private static final int WRITE_ONLY_FLOAT_PROPERTY_ID = 32345;
119     private static final int WRITE_ONLY_ENUM_PROPERTY_ID = 112345;
120     private static final int WRITE_ONLY_OTHER_ENUM_PROPERTY_ID =
121             VehiclePropertyIds.CRUISE_CONTROL_TYPE;
122     private static final int READ_WRITE_INT_PROPERTY_ID = 42345;
123     private static final int ON_CHANGE_ZONED_PROPERTY_ID = 52345;
124     private static final int CONTINUOUS_ZONED_PROPERTY_ID = 62345;
125 
126     private static final int ON_CHANGE_READ_WRITE_PROPERTY_ID = 1111;
127     private static final int NO_PERMISSION_PROPERTY_ID = 13292;
128     private static final int GLOBAL_AREA_ID = 0;
129     private static final int NOT_SUPPORTED_AREA_ID = -1;
130     private static final float MIN_SAMPLE_RATE = 2;
131     private static final float MAX_SAMPLE_RATE = 10;
132     private static final long ASYNC_TIMEOUT_MS = 1000L;
133     private static final int MIN_INT_VALUE = 10;
134     private static final int MAX_INT_VALUE = 20;
135     private static final long MIN_LONG_VALUE = 100;
136     private static final long MAX_LONG_VALUE = 200;
137     private static final float MIN_FLOAT_VALUE = 0.5f;
138     private static final float MAX_FLOAT_VALUE = 5.5f;
139     private static final List<Integer> SUPPORTED_ENUM_VALUES = List.of(-1, 0, 1, 2, 4, 5);
140     private static final Integer UNSUPPORTED_ENUM_VALUE = 3;
141     private static final Integer SUPPORTED_ERROR_STATE_ENUM_VALUE = -1;
142     private static final Integer SUPPORTED_OTHER_STATE_ENUM_VALUE = 0;
143     private static final int TEST_VALUE = 18;
144     private static final CarPropertyValue TEST_PROPERTY_VALUE = new CarPropertyValue(
145             READ_WRITE_INT_PROPERTY_ID, /* areaId= */ 0, TEST_VALUE);
146 
147     @Before
setUp()148     public void setUp() {
149         MockitoAnnotations.initMocks(this);
150 
151         when(mICarPropertyEventListener.asBinder()).thenReturn(mIBinder);
152 
153         mConfigs.put(SPEED_ID, CarPropertyConfig.newBuilder(Float.class, SPEED_ID,
154                 VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL, 1).addAreaIdConfig(
155                         new AreaIdConfig.Builder<Float>(
156                                 CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ, GLOBAL_AREA_ID)
157                         .setSupportVariableUpdateRate(true).build())
158                 .setAccess(CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ).setChangeMode(
159                 CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS).setMaxSampleRate(
160                 100).setMinSampleRate(1).build());
161         when(mHalService.isReadable(mContext, SPEED_ID)).thenReturn(true);
162         // HVAC_TEMP is actually not a global property, but for simplicity, make it global here.
163         mConfigs.put(HVAC_TEMP, CarPropertyConfig.newBuilder(Float.class, HVAC_TEMP,
164                         VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL)
165                 .addAreaIdConfig(new AreaIdConfig.Builder(
166                         CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ, GLOBAL_AREA_ID).build())
167                 .setAccess(CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ).build());
168         when(mHalService.isReadable(mContext, HVAC_TEMP)).thenReturn(true);
169         mConfigs.put(VehiclePropertyIds.GEAR_SELECTION,
170                 CarPropertyConfig.newBuilder(Integer.class, VehiclePropertyIds.GEAR_SELECTION,
171                                 VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL)
172                         .addAreaIdConfig(new AreaIdConfig.Builder(
173                                 CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ, GLOBAL_AREA_ID)
174                                 .build())
175                         .setAccess(CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ).build());
176         // Property with read or read/write access
177         when(mHalService.isReadable(mContext, CONTINUOUS_READ_ONLY_PROPERTY_ID))
178                 .thenReturn(true);
179         mConfigs.put(CONTINUOUS_READ_ONLY_PROPERTY_ID, CarPropertyConfig.newBuilder(Integer.class,
180                 CONTINUOUS_READ_ONLY_PROPERTY_ID, VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
181                 1).addAreaIdConfig(new AreaIdConfig.Builder<Integer>(
182                         CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ, GLOBAL_AREA_ID)
183                         .setSupportVariableUpdateRate(true).build()).setAccess(
184                 CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ).setChangeMode(
185                 CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS).setMinSampleRate(
186                 MIN_SAMPLE_RATE).setMaxSampleRate(MAX_SAMPLE_RATE).build());
187         when(mHalService.isWritable(mContext, WRITE_ONLY_INT_PROPERTY_ID))
188                 .thenReturn(true);
189         mConfigs.put(WRITE_ONLY_INT_PROPERTY_ID, CarPropertyConfig.newBuilder(Integer.class,
190                 WRITE_ONLY_INT_PROPERTY_ID, VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL, 1)
191                 .addAreaIdConfig(new AreaIdConfig.Builder<Integer>(
192                         CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE, GLOBAL_AREA_ID)
193                         .setMinValue(MIN_INT_VALUE).setMaxValue(MAX_INT_VALUE).build())
194                 .setAccess(CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE)
195                 .build());
196         when(mHalService.isWritable(mContext, WRITE_ONLY_LONG_PROPERTY_ID))
197                 .thenReturn(true);
198         mConfigs.put(WRITE_ONLY_LONG_PROPERTY_ID, CarPropertyConfig.newBuilder(Long.class,
199                 WRITE_ONLY_LONG_PROPERTY_ID, VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL, 1)
200                 .addAreaIdConfig(new AreaIdConfig.Builder<Long>(
201                         CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE, GLOBAL_AREA_ID)
202                         .setMinValue(MIN_LONG_VALUE).setMaxValue(MAX_LONG_VALUE).build())
203                 .setAccess(CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE)
204                 .build());
205         when(mHalService.isWritable(mContext, WRITE_ONLY_FLOAT_PROPERTY_ID))
206                 .thenReturn(true);
207         mConfigs.put(WRITE_ONLY_FLOAT_PROPERTY_ID, CarPropertyConfig.newBuilder(Float.class,
208                 WRITE_ONLY_FLOAT_PROPERTY_ID, VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL, 1)
209                 .addAreaIdConfig(new AreaIdConfig.Builder<Float>(
210                         CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE, GLOBAL_AREA_ID)
211                         .setMinValue(MIN_FLOAT_VALUE).setMaxValue(MAX_FLOAT_VALUE).build())
212                 .setAccess(CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE)
213                 .build());
214         when(mHalService.isWritable(mContext, WRITE_ONLY_ENUM_PROPERTY_ID))
215                 .thenReturn(true);
216         mConfigs.put(WRITE_ONLY_ENUM_PROPERTY_ID, CarPropertyConfig.newBuilder(Integer.class,
217                 WRITE_ONLY_ENUM_PROPERTY_ID, VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
218                 1).addAreaIdConfig(new AreaIdConfig.Builder(
219                         CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE, GLOBAL_AREA_ID)
220                 .setSupportedEnumValues(SUPPORTED_ENUM_VALUES).build())
221                 .setAccess(CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE).build());
222         when(mHalService.isWritable(mContext, WRITE_ONLY_OTHER_ENUM_PROPERTY_ID))
223                 .thenReturn(true);
224         mConfigs.put(WRITE_ONLY_OTHER_ENUM_PROPERTY_ID, CarPropertyConfig.newBuilder(Integer.class,
225                 WRITE_ONLY_OTHER_ENUM_PROPERTY_ID, VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
226                 1).addAreaIdConfig(new AreaIdConfig.Builder(
227                         CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE, GLOBAL_AREA_ID)
228                 .setSupportedEnumValues(SUPPORTED_ENUM_VALUES).build())
229                 .setAccess(CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE).build());
230         when(mHalService.isReadable(mContext, ON_CHANGE_READ_WRITE_PROPERTY_ID))
231                 .thenReturn(true);
232         when(mHalService.isWritable(mContext, ON_CHANGE_READ_WRITE_PROPERTY_ID))
233                 .thenReturn(true);
234         mConfigs.put(ON_CHANGE_READ_WRITE_PROPERTY_ID, CarPropertyConfig.newBuilder(Integer.class,
235                 ON_CHANGE_READ_WRITE_PROPERTY_ID, VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL, 1)
236                 .addAreaIdConfig(new AreaIdConfig.Builder(
237                         CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE, GLOBAL_AREA_ID)
238                         .build())
239                 .setAccess(CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE)
240                 .setChangeMode(CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE).build());
241         mConfigs.put(NO_PERMISSION_PROPERTY_ID, CarPropertyConfig.newBuilder(Integer.class,
242                 NO_PERMISSION_PROPERTY_ID, VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL, 1)
243                 .addAreaIdConfig(new AreaIdConfig.Builder(
244                         CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE, GLOBAL_AREA_ID)
245                         .build())
246                 .setAccess(CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE).build());
247 
248         when(mHalService.isReadable(mContext, READ_WRITE_INT_PROPERTY_ID))
249                 .thenReturn(true);
250         when(mHalService.isWritable(mContext, READ_WRITE_INT_PROPERTY_ID))
251                 .thenReturn(true);
252         mConfigs.put(READ_WRITE_INT_PROPERTY_ID, CarPropertyConfig.newBuilder(Integer.class,
253                 READ_WRITE_INT_PROPERTY_ID, VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL, 1)
254                 .addAreaIdConfig(new AreaIdConfig.Builder<Integer>(
255                         CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE, GLOBAL_AREA_ID)
256                         .setMinValue(MIN_INT_VALUE).setMaxValue(MAX_INT_VALUE).build())
257                 .setAccess(CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE)
258                 .build());
259 
260         mConfigs.put(ON_CHANGE_ZONED_PROPERTY_ID, CarPropertyConfig.newBuilder(Float.class,
261                 ON_CHANGE_ZONED_PROPERTY_ID, VehicleAreaType.VEHICLE_AREA_TYPE_WINDOW, 1)
262                 .addAreaIdConfig(new AreaIdConfig.Builder<Float>(
263                         CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
264                         VehicleAreaWindow.WINDOW_ROW_1_LEFT).build())
265                 .addAreaIdConfig(new AreaIdConfig.Builder<Float>(
266                         CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
267                         VehicleAreaWindow.WINDOW_ROW_1_RIGHT).build())
268                 .setAccess(CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ)
269                 .setChangeMode(CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE)
270                 .setMaxSampleRate(100)
271                 .setMinSampleRate(1)
272                 .build());
273         when(mHalService.isReadable(mContext, ON_CHANGE_ZONED_PROPERTY_ID)).thenReturn(true);
274 
275         mConfigs.put(CONTINUOUS_ZONED_PROPERTY_ID, CarPropertyConfig.newBuilder(Float.class,
276                 CONTINUOUS_ZONED_PROPERTY_ID, VehicleAreaType.VEHICLE_AREA_TYPE_WINDOW, 1)
277                 .addAreaIdConfig(new AreaIdConfig.Builder<Float>(
278                         CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
279                         VehicleAreaWindow.WINDOW_ROW_1_LEFT).build())
280                 .addAreaIdConfig(new AreaIdConfig.Builder<Float>(
281                         CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
282                         VehicleAreaWindow.WINDOW_ROW_1_RIGHT).build())
283                 .setAccess(CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ)
284                 .setChangeMode(CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS)
285                 .setMaxSampleRate(100)
286                 .setMinSampleRate(1)
287                 .build());
288         when(mHalService.isReadable(mContext, CONTINUOUS_ZONED_PROPERTY_ID)).thenReturn(true);
289 
290         when(mHalService.getPropertyList()).thenReturn(mConfigs);
291 
292         when(mFeatureFlags.variableUpdateRate()).thenReturn(true);
293         when(mFeatureFlags.subscriptionWithResolution()).thenReturn(true);
294 
295         when(mHistogramFactory.newUniformHistogram(any(), anyInt(), anyFloat(), anyFloat()))
296                 .thenReturn(mock(Histogram.class));
297         when(mHistogramFactory.newScaledRangeHistogram(any(), anyInt(), anyInt(), anyFloat(),
298                 anyFloat())).thenReturn(mock(Histogram.class));
299 
300         mService = new CarPropertyService.Builder()
301                 .setContext(mContext)
302                 .setPropertyHalService(mHalService)
303                 .setFeatureFlags(mFeatureFlags)
304                 .setHistogramFactory(mHistogramFactory)
305                 .build();
306         mService.init();
307     }
308 
309     @Test
testGetPropertyConfigList()310     public void testGetPropertyConfigList() {
311         var result = mService.getPropertyConfigList(new int[] {
312                 READ_WRITE_INT_PROPERTY_ID, ON_CHANGE_ZONED_PROPERTY_ID});
313 
314         assertThat(result.missingPermissionPropIds.length).isEqualTo(0);
315         assertThat(result.unsupportedPropIds.length).isEqualTo(0);
316         assertThat(result.carPropertyConfigList.getConfigs()).containsExactly(
317                 mConfigs.get(READ_WRITE_INT_PROPERTY_ID),
318                 mConfigs.get(ON_CHANGE_ZONED_PROPERTY_ID));
319     }
320 
321     @Test
testGetPropertyConfigList_noReadOrWritePermission()322     public void testGetPropertyConfigList_noReadOrWritePermission() {
323         var result = mService.getPropertyConfigList(new int[] {
324                 READ_WRITE_INT_PROPERTY_ID, NO_PERMISSION_PROPERTY_ID});
325 
326         assertThat(result.missingPermissionPropIds).isEqualTo(new int[] {
327                 NO_PERMISSION_PROPERTY_ID});
328         assertThat(result.unsupportedPropIds.length).isEqualTo(0);
329         assertThat(result.carPropertyConfigList.getConfigs()).containsExactly(
330                 mConfigs.get(READ_WRITE_INT_PROPERTY_ID));
331     }
332 
333     @Test
testGetPropertyConfigList_noConfig()334     public void testGetPropertyConfigList_noConfig() {
335         int invalidPropertyID = -1;
336         var result = mService.getPropertyConfigList(new int[] {
337                 READ_WRITE_INT_PROPERTY_ID, invalidPropertyID});
338 
339         assertThat(result.unsupportedPropIds).isEqualTo(new int[] {invalidPropertyID});
340         assertThat(result.missingPermissionPropIds.length).isEqualTo(0);
341         assertThat(result.carPropertyConfigList.getConfigs()).containsExactly(
342                 mConfigs.get(READ_WRITE_INT_PROPERTY_ID));
343     }
344 
345     @Test
testGetPropertiesAsync()346     public void testGetPropertiesAsync() {
347         AsyncPropertyServiceRequest getPropertyServiceRequest = new AsyncPropertyServiceRequest(0,
348                 SPEED_ID, 0);
349         List<AsyncPropertyServiceRequest> requests = List.of(getPropertyServiceRequest);
350 
351         mService.getPropertiesAsync(new AsyncPropertyServiceRequestList(requests),
352                 mAsyncPropertyResultCallback, ASYNC_TIMEOUT_MS);
353 
354         verify(mHalService).getCarPropertyValuesAsync(eq(requests), any(), eq(ASYNC_TIMEOUT_MS),
355                 anyLong());
356     }
357 
358     @Test
testGetPropertiesAsync_throwsExceptionBecauseOfNullRequests()359     public void testGetPropertiesAsync_throwsExceptionBecauseOfNullRequests() {
360         assertThrows(NullPointerException.class,
361                 () -> mService.getPropertiesAsync(null, mAsyncPropertyResultCallback,
362                         ASYNC_TIMEOUT_MS));
363     }
364 
365     @Test
testGetPropertiesAsync_throwsExceptionBecauseOfNullCallback()366     public void testGetPropertiesAsync_throwsExceptionBecauseOfNullCallback() {
367         assertThrows(NullPointerException.class,
368                 () -> mService.getPropertiesAsync(new AsyncPropertyServiceRequestList(List.of()),
369                         null, ASYNC_TIMEOUT_MS));
370     }
371 
372     @Test
testGetPropertiesAsync_propertyIdNotSupported()373     public void testGetPropertiesAsync_propertyIdNotSupported() {
374         int invalidPropertyID = -1;
375         AsyncPropertyServiceRequest getPropertyServiceRequest = new AsyncPropertyServiceRequest(0,
376                 invalidPropertyID, 0);
377 
378         assertThrows(IllegalArgumentException.class, () -> mService.getPropertiesAsync(
379                 new AsyncPropertyServiceRequestList(List.of(getPropertyServiceRequest)),
380                 mAsyncPropertyResultCallback, ASYNC_TIMEOUT_MS));
381     }
382 
383     @Test
testGetPropertiesAsync_noReadPermission()384     public void testGetPropertiesAsync_noReadPermission() {
385         AsyncPropertyServiceRequest getPropertyServiceRequest = new AsyncPropertyServiceRequest(0,
386                 SPEED_ID, 0);
387         when(mHalService.isReadable(mContext, SPEED_ID)).thenReturn(false);
388 
389         assertThrows(SecurityException.class, () -> mService.getPropertiesAsync(
390                 new AsyncPropertyServiceRequestList(List.of(getPropertyServiceRequest)),
391                 mAsyncPropertyResultCallback, ASYNC_TIMEOUT_MS));
392     }
393 
394     @Test
testGetPropertiesAsync_propertyNotReadable()395     public void testGetPropertiesAsync_propertyNotReadable() {
396         AsyncPropertyServiceRequest getPropertyServiceRequest = new AsyncPropertyServiceRequest(0,
397                 WRITE_ONLY_INT_PROPERTY_ID, 0);
398 
399         assertThrows(IllegalArgumentException.class, () -> mService.getPropertiesAsync(
400                 new AsyncPropertyServiceRequestList(List.of(getPropertyServiceRequest)),
401                 mAsyncPropertyResultCallback, ASYNC_TIMEOUT_MS));
402     }
403 
404     @Test
testGetPropertiesAsync_areaIdNotSupported()405     public void testGetPropertiesAsync_areaIdNotSupported() {
406         AsyncPropertyServiceRequest getPropertyServiceRequest = new AsyncPropertyServiceRequest(0,
407                 HVAC_TEMP, NOT_SUPPORTED_AREA_ID);
408 
409         assertThrows(IllegalArgumentException.class, () -> mService.getPropertiesAsync(
410                 new AsyncPropertyServiceRequestList(List.of(getPropertyServiceRequest)),
411                 mAsyncPropertyResultCallback, ASYNC_TIMEOUT_MS));
412     }
413 
414     @Test
testGetPropertiesAsync_timeoutNotPositiveNumber()415     public void testGetPropertiesAsync_timeoutNotPositiveNumber() {
416         AsyncPropertyServiceRequest getPropertyServiceRequest = new AsyncPropertyServiceRequest(0,
417                 SPEED_ID, 0);
418 
419         assertThrows(IllegalArgumentException.class, () -> mService.getPropertiesAsync(
420                 new AsyncPropertyServiceRequestList(List.of(getPropertyServiceRequest)),
421                 mAsyncPropertyResultCallback, /* timeoutInMs= */ 0));
422     }
423 
424     @Test
testSetPropertiesAsync()425     public void testSetPropertiesAsync() {
426         AsyncPropertyServiceRequest request = new AsyncPropertyServiceRequest(
427                 0, READ_WRITE_INT_PROPERTY_ID, 0, TEST_PROPERTY_VALUE);
428         List<AsyncPropertyServiceRequest> requests = List.of(request);
429 
430         mService.setPropertiesAsync(new AsyncPropertyServiceRequestList(requests),
431                 mAsyncPropertyResultCallback, ASYNC_TIMEOUT_MS);
432 
433         verify(mHalService).setCarPropertyValuesAsync(eq(requests), any(), eq(ASYNC_TIMEOUT_MS),
434                 anyLong());
435     }
436 
437     @Test
testSetPropertiesAsync_noWaitForPropertyUpdate()438     public void testSetPropertiesAsync_noWaitForPropertyUpdate() {
439         AsyncPropertyServiceRequest request = new AsyncPropertyServiceRequest(
440                 0, WRITE_ONLY_INT_PROPERTY_ID, 0, new CarPropertyValue(WRITE_ONLY_INT_PROPERTY_ID,
441                         /* areaId= */ 0, /* value= */ 13));
442         request.setWaitForPropertyUpdate(false);
443         List<AsyncPropertyServiceRequest> requests = List.of(request);
444 
445         mService.setPropertiesAsync(new AsyncPropertyServiceRequestList(requests),
446                 mAsyncPropertyResultCallback, ASYNC_TIMEOUT_MS);
447 
448         verify(mHalService).setCarPropertyValuesAsync(eq(requests), any(), eq(ASYNC_TIMEOUT_MS),
449                 anyLong());
450     }
451 
452     @Test
testSetPropertiesAsync_throwsExceptionBecauseOfNullRequests()453     public void testSetPropertiesAsync_throwsExceptionBecauseOfNullRequests() {
454         assertThrows(NullPointerException.class,
455                 () -> mService.setPropertiesAsync(null, mAsyncPropertyResultCallback,
456                         ASYNC_TIMEOUT_MS));
457     }
458 
459     @Test
testSetPropertiesAsync_throwsExceptionBecauseOfNullCallback()460     public void testSetPropertiesAsync_throwsExceptionBecauseOfNullCallback() {
461         assertThrows(NullPointerException.class,
462                 () -> mService.setPropertiesAsync(new AsyncPropertyServiceRequestList(List.of()),
463                         null, ASYNC_TIMEOUT_MS));
464     }
465 
466     @Test
testSetPropertiesAsync_mismatchPropertyId()467     public void testSetPropertiesAsync_mismatchPropertyId() {
468         AsyncPropertyServiceRequest request = new AsyncPropertyServiceRequest(
469                 0, READ_WRITE_INT_PROPERTY_ID, 0, new CarPropertyValue(SPEED_ID, 0, 1));
470 
471         assertThrows(IllegalArgumentException.class, () -> mService.setPropertiesAsync(
472                 new AsyncPropertyServiceRequestList(List.of(request)),
473                 mAsyncPropertyResultCallback, ASYNC_TIMEOUT_MS));
474     }
475 
476     @Test
testSetPropertiesAsync_mismatchAreaId()477     public void testSetPropertiesAsync_mismatchAreaId() {
478         AsyncPropertyServiceRequest request = new AsyncPropertyServiceRequest(
479                 0, READ_WRITE_INT_PROPERTY_ID, 0, new CarPropertyValue(
480                         READ_WRITE_INT_PROPERTY_ID, /* areaId= */ 1, 1));
481 
482         assertThrows(IllegalArgumentException.class, () -> mService.setPropertiesAsync(
483                 new AsyncPropertyServiceRequestList(List.of(request)),
484                 mAsyncPropertyResultCallback, ASYNC_TIMEOUT_MS));
485     }
486 
487     @Test
testSetPropertiesAsync_nullPropertyValueToSet()488     public void testSetPropertiesAsync_nullPropertyValueToSet() {
489         AsyncPropertyServiceRequest request = new AsyncPropertyServiceRequest(
490                 0, READ_WRITE_INT_PROPERTY_ID, 0, /* carPropertyValue= */ null);
491 
492         assertThrows(NullPointerException.class, () -> mService.setPropertiesAsync(
493                 new AsyncPropertyServiceRequestList(List.of(request)),
494                 mAsyncPropertyResultCallback, ASYNC_TIMEOUT_MS));
495     }
496 
497     @Test
testSetPropertiesAsync_nullValueToSet()498     public void testSetPropertiesAsync_nullValueToSet() {
499         AsyncPropertyServiceRequest request = new AsyncPropertyServiceRequest(
500                 0, READ_WRITE_INT_PROPERTY_ID, 0, new CarPropertyValue(READ_WRITE_INT_PROPERTY_ID,
501                         0, /* value= */ null));
502 
503         assertThrows(IllegalArgumentException.class, () -> mService.setPropertiesAsync(
504                 new AsyncPropertyServiceRequestList(List.of(request)),
505                 mAsyncPropertyResultCallback, ASYNC_TIMEOUT_MS));
506     }
507 
508     @Test
testSetPropertiesAsync_mismatchPropertyType()509     public void testSetPropertiesAsync_mismatchPropertyType() {
510         AsyncPropertyServiceRequest request = new AsyncPropertyServiceRequest(
511                 0, READ_WRITE_INT_PROPERTY_ID, 0, new CarPropertyValue(READ_WRITE_INT_PROPERTY_ID,
512                         0, Float.valueOf(1.1f)));
513 
514         assertThrows(IllegalArgumentException.class, () -> mService.setPropertiesAsync(
515                 new AsyncPropertyServiceRequestList(List.of(request)),
516                 mAsyncPropertyResultCallback, ASYNC_TIMEOUT_MS));
517     }
518 
519     @Test
testSetPropertiesAsync_valueLargerThanMaxValue()520     public void testSetPropertiesAsync_valueLargerThanMaxValue() {
521         AsyncPropertyServiceRequest request = new AsyncPropertyServiceRequest(
522                 0, READ_WRITE_INT_PROPERTY_ID, 0, new CarPropertyValue(READ_WRITE_INT_PROPERTY_ID,
523                         0, MAX_INT_VALUE + 1));
524 
525         assertThrows(IllegalArgumentException.class, () -> mService.setPropertiesAsync(
526                 new AsyncPropertyServiceRequestList(List.of(request)),
527                 mAsyncPropertyResultCallback, ASYNC_TIMEOUT_MS));
528     }
529 
530     @Test
testSetPropertiesAsync_valueSmallerThanMinValue()531     public void testSetPropertiesAsync_valueSmallerThanMinValue() {
532         AsyncPropertyServiceRequest request = new AsyncPropertyServiceRequest(
533                 0, READ_WRITE_INT_PROPERTY_ID, 0, new CarPropertyValue(READ_WRITE_INT_PROPERTY_ID,
534                         0, MIN_INT_VALUE - 1));
535 
536         assertThrows(IllegalArgumentException.class, () -> mService.setPropertiesAsync(
537                 new AsyncPropertyServiceRequestList(List.of(request)),
538                 mAsyncPropertyResultCallback, ASYNC_TIMEOUT_MS));
539     }
540 
541     @Test
testSetPropertiesAsync_propertyIdNotSupported()542     public void testSetPropertiesAsync_propertyIdNotSupported() {
543         int invalidPropertyID = -1;
544         AsyncPropertyServiceRequest request = new AsyncPropertyServiceRequest(
545                 0, invalidPropertyID, 0, TEST_PROPERTY_VALUE);
546 
547         assertThrows(IllegalArgumentException.class, () -> mService.setPropertiesAsync(
548                 new AsyncPropertyServiceRequestList(List.of(request)),
549                 mAsyncPropertyResultCallback, ASYNC_TIMEOUT_MS));
550     }
551 
552     @Test
testSetPropertiesAsync_noWritePermission()553     public void testSetPropertiesAsync_noWritePermission() {
554         AsyncPropertyServiceRequest request = new AsyncPropertyServiceRequest(
555                 0, READ_WRITE_INT_PROPERTY_ID, 0, TEST_PROPERTY_VALUE);
556         when(mHalService.isWritable(mContext, READ_WRITE_INT_PROPERTY_ID))
557                 .thenReturn(false);
558 
559         assertThrows(SecurityException.class, () -> mService.setPropertiesAsync(
560                 new AsyncPropertyServiceRequestList(List.of(request)),
561                 mAsyncPropertyResultCallback, ASYNC_TIMEOUT_MS));
562     }
563 
564     @Test
testSetPropertiesAsync_propertyNotWrittable()565     public void testSetPropertiesAsync_propertyNotWrittable() {
566         AsyncPropertyServiceRequest request = new AsyncPropertyServiceRequest(0,
567                 SPEED_ID, 0, TEST_PROPERTY_VALUE);
568 
569         assertThrows(IllegalArgumentException.class, () -> mService.setPropertiesAsync(
570                 new AsyncPropertyServiceRequestList(List.of(request)),
571                 mAsyncPropertyResultCallback, ASYNC_TIMEOUT_MS));
572     }
573 
574     // By default waitForPropertyUpdate is true, which requires the read permisison as well.
575     @Test
testSetPropertiesAsync_noReadPermission()576     public void testSetPropertiesAsync_noReadPermission() {
577         AsyncPropertyServiceRequest request = new AsyncPropertyServiceRequest(
578                 0, READ_WRITE_INT_PROPERTY_ID, 0, TEST_PROPERTY_VALUE);
579         when(mHalService.isReadable(mContext, READ_WRITE_INT_PROPERTY_ID))
580                 .thenReturn(false);
581 
582         assertThrows(SecurityException.class, () -> mService.setPropertiesAsync(
583                 new AsyncPropertyServiceRequestList(List.of(request)),
584                 mAsyncPropertyResultCallback, ASYNC_TIMEOUT_MS));
585     }
586 
587     // By default waitForPropertyUpdate is true, which requires the read permisison as well.
588     @Test
testSetPropertiesAsync_propertyNotReadable()589     public void testSetPropertiesAsync_propertyNotReadable() {
590         AsyncPropertyServiceRequest request = new AsyncPropertyServiceRequest(0,
591                 WRITE_ONLY_INT_PROPERTY_ID, 0, TEST_PROPERTY_VALUE);
592 
593         assertThrows(IllegalArgumentException.class, () -> mService.setPropertiesAsync(
594                 new AsyncPropertyServiceRequestList(List.of(request)),
595                 mAsyncPropertyResultCallback, ASYNC_TIMEOUT_MS));
596     }
597 
598     @Test
testSetPropertiesAsync_areaIdNotSupported()599     public void testSetPropertiesAsync_areaIdNotSupported() {
600         AsyncPropertyServiceRequest request = new AsyncPropertyServiceRequest(
601                 0, READ_WRITE_INT_PROPERTY_ID, NOT_SUPPORTED_AREA_ID, TEST_PROPERTY_VALUE);
602 
603         assertThrows(IllegalArgumentException.class, () -> mService.setPropertiesAsync(
604                 new AsyncPropertyServiceRequestList(List.of(request)),
605                 mAsyncPropertyResultCallback, ASYNC_TIMEOUT_MS));
606     }
607 
608     @Test
testSetPropertiesAsync_timeoutNotPositiveNumber()609     public void testSetPropertiesAsync_timeoutNotPositiveNumber() {
610         AsyncPropertyServiceRequest request = new AsyncPropertyServiceRequest(
611                 0, READ_WRITE_INT_PROPERTY_ID, 0, TEST_PROPERTY_VALUE);
612 
613         assertThrows(IllegalArgumentException.class, () -> mService.setPropertiesAsync(
614                 new AsyncPropertyServiceRequestList(List.of(request)),
615                 mAsyncPropertyResultCallback, /* timeoutInMs= */ 0));
616     }
617 
618     @Test
testRegisterUnregisterForContinuousProperty()619     public void testRegisterUnregisterForContinuousProperty() throws Exception {
620         ICarPropertyEventListener mMockHandler1 = mock(ICarPropertyEventListener.class);
621         ICarPropertyEventListener mMockHandler2 = mock(ICarPropertyEventListener.class);
622         // Must use two different binders because listener is uniquely identified by binder.
623         IBinder mBinder1 = mock(IBinder.class);
624         IBinder mBinder2 = mock(IBinder.class);
625         when(mMockHandler1.asBinder()).thenReturn(mBinder1);
626         when(mMockHandler2.asBinder()).thenReturn(mBinder2);
627         long timestampNanos = Duration.ofSeconds(1).toNanos();
628         CarPropertyValue<Float> mValue =
629                 new CarPropertyValue<>(SPEED_ID, 0, timestampNanos, 0f);
630         when(mHalService.getProperty(SPEED_ID, 0)).thenReturn(mValue);
631 
632         // Register the first listener.
633         mService.registerListener(SPEED_ID, /* updateRateHz= */ 10, mMockHandler1);
634 
635         // Wait until we get the on property change event for the initial value.
636         verify(mMockHandler1, timeout(5000)).onEvent(any());
637 
638         verify(mHalService).subscribeProperty(List.of(createCarSubscriptionOption(SPEED_ID,
639                 new int[]{0}, 10f, /* enableVur= */ true)));
640         verify(mHalService).getProperty(SPEED_ID, 0);
641 
642         // Clean up invocation state.
643         clearInvocations(mHalService);
644 
645         // Register the second listener.
646         mService.registerListener(SPEED_ID, /* updateRateHz= */ 20, mMockHandler2);
647 
648         // Wait until we get the on property change event for the initial value.
649         verify(mMockHandler2, timeout(5000)).onEvent(any());
650 
651         verify(mHalService).subscribeProperty(List.of(createCarSubscriptionOption(SPEED_ID,
652                 new int[]{0}, 20f, /* enableVur= */ true)));
653         verify(mHalService).getProperty(SPEED_ID, 0);
654 
655         // Clean up invocation state.
656         clearInvocations(mHalService);
657 
658         // Unregister the second listener, the first listener must still be registered.
659         mService.unregisterListener(SPEED_ID, mMockHandler2);
660 
661         // The property must not be unsubscribed.
662         verify(mHalService, never()).unsubscribeProperty(anyInt());
663         // The subscription rate must be updated.
664         verify(mHalService).subscribeProperty(List.of(createCarSubscriptionOption(SPEED_ID,
665                 new int[]{0}, 10f, /* enableVur= */ true)));
666 
667         // Unregister the first listener. We have no more listeners, must cause unsubscription.
668         mService.unregisterListener(SPEED_ID, mMockHandler1);
669 
670         verify(mHalService).unsubscribeProperty(SPEED_ID);
671     }
672 
673     @Test
testRegisterForMultipleProperties()674     public void testRegisterForMultipleProperties() throws Exception {
675         ICarPropertyEventListener mMockHandler1 = mock(ICarPropertyEventListener.class);
676         // Must use two different binders because listener is uniquely identified by binder.
677         IBinder mBinder1 = mock(IBinder.class);
678         when(mMockHandler1.asBinder()).thenReturn(mBinder1);
679         long timestampNanos = Duration.ofSeconds(1).toNanos();
680         CarPropertyValue<Float> mValue =
681                 new CarPropertyValue<>(HVAC_TEMP, 0, timestampNanos, 0f);
682         when(mHalService.getProperty(HVAC_TEMP, 0)).thenReturn(mValue);
683         mValue = new CarPropertyValue<>(SPEED_ID, 0, timestampNanos, 0f);
684         when(mHalService.getProperty(SPEED_ID, 0)).thenReturn(mValue);
685 
686         mService.registerListener(List.of(
687                 createCarSubscriptionOption(SPEED_ID, new int[]{0}, 20f),
688                         createCarSubscriptionOption(HVAC_TEMP, new int[]{0}, 0f)),
689                 mMockHandler1);
690         verify(mMockHandler1, timeout(5000)).onEvent(any());
691 
692         verify(mHalService).subscribeProperty(List.of(
693                 createCarSubscriptionOption(SPEED_ID, new int[]{0}, 20f),
694                 createCarSubscriptionOption(HVAC_TEMP, new int[]{0}, 0f)));
695         verify(mHalService).getProperty(HVAC_TEMP, 0);
696         verify(mHalService).getProperty(SPEED_ID, 0);
697 
698         // Clean up invocation state.
699         clearInvocations(mHalService);
700 
701         // Unregister the second listener, the first listener must still be registered.
702         mService.unregisterListener(HVAC_TEMP, mMockHandler1);
703         verify(mHalService).unsubscribeProperty(HVAC_TEMP);
704 
705         // Clean up invocation state.
706         clearInvocations(mHalService);
707 
708         mService.unregisterListener(SPEED_ID, mMockHandler1);
709         verify(mHalService).unsubscribeProperty(SPEED_ID);
710     }
711 
712     @Test
testRegisterUnregisterForOnChangeProperty()713     public void testRegisterUnregisterForOnChangeProperty() throws Exception {
714         ICarPropertyEventListener mMockHandler1 = mock(ICarPropertyEventListener.class);
715         ICarPropertyEventListener mMockHandler2 = mock(ICarPropertyEventListener.class);
716         // Must use two different binders because listener is uniquely identified by binder.
717         IBinder mBinder1 = mock(IBinder.class);
718         IBinder mBinder2 = mock(IBinder.class);
719         when(mMockHandler1.asBinder()).thenReturn(mBinder1);
720         when(mMockHandler2.asBinder()).thenReturn(mBinder2);
721         long timestampNanos = Duration.ofSeconds(1).toNanos();
722         CarPropertyValue<Float> mValue =
723                 new CarPropertyValue<>(HVAC_TEMP, 0, timestampNanos, 0f);
724         when(mHalService.getProperty(HVAC_TEMP, 0)).thenReturn(mValue);
725 
726         // Register the first listener.
727         mService.registerListener(HVAC_TEMP, /* updateRateHz= */ SENSOR_RATE_ONCHANGE,
728                 mMockHandler1);
729 
730         // Wait until we get the on property change event for the initial value.
731         verify(mMockHandler1, timeout(5000)).onEvent(any());
732 
733         verify(mHalService).subscribeProperty(List.of(createCarSubscriptionOption(
734                 HVAC_TEMP, new int[]{0}, 0f)));
735         verify(mHalService).getProperty(HVAC_TEMP, 0);
736 
737         // Clean up invocation state.
738         clearInvocations(mHalService);
739 
740         // Register the second listener.
741         mService.registerListener(HVAC_TEMP, /* updateRateHz= */ SENSOR_RATE_ONCHANGE,
742                 mMockHandler2);
743 
744         // Wait until we get the on property change event for the initial value.
745         verify(mMockHandler2, timeout(5000)).onEvent(any());
746 
747         // Must not subscribe again.
748         verify(mHalService, never()).subscribeProperty(any());
749         verify(mHalService).getProperty(HVAC_TEMP, 0);
750 
751         // Clean up invocation state.
752         clearInvocations(mHalService);
753 
754         // Unregister the second listener, the first listener must still be registered.
755         mService.unregisterListener(HVAC_TEMP, mMockHandler2);
756 
757         // The property must not be unsubscribed.
758         verify(mHalService, never()).unsubscribeProperty(anyInt());
759 
760         // Unregister the first listener. We have no more listeners, must cause unsubscription.
761         mService.unregisterListener(HVAC_TEMP, mMockHandler1);
762 
763         verify(mHalService).unsubscribeProperty(HVAC_TEMP);
764     }
765 
766     @Test
testRegisterListenerWithSubscription()767     public void testRegisterListenerWithSubscription() throws Exception {
768         ICarPropertyEventListener mockHandler = mock(ICarPropertyEventListener.class);
769         IBinder mockBinder = mock(IBinder.class);
770         when(mockHandler.asBinder()).thenReturn(mockBinder);
771         long timestampNanos = Duration.ofSeconds(1).toNanos();
772         CarPropertyValue<Float> speedValue = new CarPropertyValue<>(
773                 SPEED_ID, 0, timestampNanos, 0f);
774         when(mHalService.getProperty(SPEED_ID, 0)).thenReturn(speedValue);
775         CarPropertyValue<Float> hvacValue = new CarPropertyValue<>(
776                 HVAC_TEMP, 0, timestampNanos, 0f);
777         when(mHalService.getProperty(HVAC_TEMP, 0)).thenReturn(hvacValue);
778 
779         List<CarSubscription> subscribeOptions = List.of(
780                 createCarSubscriptionOption(SPEED_ID, new int[]{0}, 20f),
781                 createCarSubscriptionOption(HVAC_TEMP, new int[]{0}, 0f));
782         mService.registerListener(subscribeOptions, mockHandler);
783 
784         // Verify the two initial value responses arrive.
785         verify(mockHandler, timeout(5000)).onEvent(mPropertyEventCaptor.capture());
786         List<CarPropertyEvent> eventList = mPropertyEventCaptor.getValue();
787         assertWithMessage("Must receive two initial value events").that(eventList).hasSize(2);
788         assertWithMessage("Received expected speed initial value event").that(
789                 eventList.get(0).getCarPropertyValue()).isEqualTo(speedValue);
790         assertWithMessage("Received expected hvac initial value event").that(
791                 eventList.get(1).getCarPropertyValue()).isEqualTo(hvacValue);
792         verify(mHalService).subscribeProperty(subscribeOptions);
793         // Verify the initial get value requests are sent.
794         verify(mHalService).getProperty(SPEED_ID, 0);
795         verify(mHalService).getProperty(HVAC_TEMP, 0);
796     }
797 
798     @Test
testRegisterListenerWithSubscription_enableVurAndResolution()799     public void testRegisterListenerWithSubscription_enableVurAndResolution() throws Exception {
800         ICarPropertyEventListener mockHandler = mock(ICarPropertyEventListener.class);
801         IBinder mockBinder = mock(IBinder.class);
802         when(mockHandler.asBinder()).thenReturn(mockBinder);
803         long timestampNanos = Duration.ofSeconds(1).toNanos();
804         CarPropertyValue<Float> speedValue = new CarPropertyValue<>(
805                 SPEED_ID, 0, timestampNanos, 0f);
806         when(mHalService.getProperty(SPEED_ID, 0)).thenReturn(speedValue);
807         CarPropertyValue<Float> hvacValue = new CarPropertyValue<>(
808                 HVAC_TEMP, 0, timestampNanos, 0f);
809         when(mHalService.getProperty(HVAC_TEMP, 0)).thenReturn(hvacValue);
810 
811         List<CarSubscription> subscribeOptions = List.of(
812                 createCarSubscriptionOption(SPEED_ID, new int[]{0}, 20f, /* enableVur= */ true,
813                         /* resolution */ 1.0f),
814                 // This is an on-change property, so Vur must have no effect.
815                 createCarSubscriptionOption(HVAC_TEMP, new int[]{0}, 0f, /* enableVur= */ true,
816                         /* resolution */ 1.0f));
817         List<CarSubscription> sanitizedOptions = List.of(
818                 createCarSubscriptionOption(SPEED_ID, new int[]{0}, 20f, /* enableVur= */ true,
819                         /* resolution */ 1.0f),
820                 // This is an on-change property, so Vur must have no effect.
821                 createCarSubscriptionOption(HVAC_TEMP, new int[]{0}, 0f, /* enableVur= */ false,
822                         /* resolution */ 0.0f));
823         mService.registerListener(subscribeOptions, mockHandler);
824 
825         // Verify the two initial value responses arrive.
826         verify(mockHandler, timeout(5000)).onEvent(mPropertyEventCaptor.capture());
827         verify(mHalService).subscribeProperty(sanitizedOptions);
828     }
829 
830     @Test
testRegisterListenerWithSubscription_VurFeatureOff()831     public void testRegisterListenerWithSubscription_VurFeatureOff() throws Exception {
832         when(mFeatureFlags.variableUpdateRate()).thenReturn(false);
833 
834         ICarPropertyEventListener mockHandler = mock(ICarPropertyEventListener.class);
835         IBinder mockBinder = mock(IBinder.class);
836         when(mockHandler.asBinder()).thenReturn(mockBinder);
837         long timestampNanos = Duration.ofSeconds(1).toNanos();
838         CarPropertyValue<Float> speedValue = new CarPropertyValue<>(
839                 SPEED_ID, 0, timestampNanos, 0f);
840         when(mHalService.getProperty(SPEED_ID, 0)).thenReturn(speedValue);
841         CarPropertyValue<Float> hvacValue = new CarPropertyValue<>(
842                 HVAC_TEMP, 0, timestampNanos, 0f);
843         when(mHalService.getProperty(HVAC_TEMP, 0)).thenReturn(hvacValue);
844 
845         List<CarSubscription> subscribeOptions = List.of(
846                 createCarSubscriptionOption(SPEED_ID, new int[]{0}, 20f, /* enableVur= */ true));
847         List<CarSubscription> sanitizedOptions = List.of(
848                 createCarSubscriptionOption(SPEED_ID, new int[]{0}, 20f, /* enableVur= */ false));
849         mService.registerListener(subscribeOptions, mockHandler);
850 
851         // Verify the two initial value responses arrive.
852         verify(mockHandler, timeout(5000)).onEvent(mPropertyEventCaptor.capture());
853         verify(mHalService).subscribeProperty(sanitizedOptions);
854     }
855 
856     @Test
testRegisterListenerWithSubscription_ResolutionFeatureOff()857     public void testRegisterListenerWithSubscription_ResolutionFeatureOff() throws Exception {
858         when(mFeatureFlags.subscriptionWithResolution()).thenReturn(false);
859 
860         ICarPropertyEventListener mockHandler = mock(ICarPropertyEventListener.class);
861         IBinder mockBinder = mock(IBinder.class);
862         when(mockHandler.asBinder()).thenReturn(mockBinder);
863         long timestampNanos = Duration.ofSeconds(1).toNanos();
864         CarPropertyValue<Float> speedValue = new CarPropertyValue<>(
865                 SPEED_ID, 0, timestampNanos, 0f);
866         when(mHalService.getProperty(SPEED_ID, 0)).thenReturn(speedValue);
867         CarPropertyValue<Float> hvacValue = new CarPropertyValue<>(
868                 HVAC_TEMP, 0, timestampNanos, 0f);
869         when(mHalService.getProperty(HVAC_TEMP, 0)).thenReturn(hvacValue);
870 
871         List<CarSubscription> subscribeOptions = List.of(
872                 createCarSubscriptionOption(SPEED_ID, new int[]{0}, 20f, /* enableVur= */ true,
873                         /* resolution */ 1.0f));
874         List<CarSubscription> sanitizedOptions = List.of(
875                 createCarSubscriptionOption(SPEED_ID, new int[]{0}, 20f, /* enableVur= */ true,
876                         /* resolution */ 0.0f));
877         mService.registerListener(subscribeOptions, mockHandler);
878 
879         // Verify the two initial value responses arrive.
880         verify(mockHandler, timeout(5000)).onEvent(mPropertyEventCaptor.capture());
881         verify(mHalService).subscribeProperty(sanitizedOptions);
882     }
883 
884     @Test
testRegisterListenerWithSubscription_exceptionFromPropertyHalService()885     public void testRegisterListenerWithSubscription_exceptionFromPropertyHalService()
886             throws Exception {
887         ICarPropertyEventListener mockHandler = mock(ICarPropertyEventListener.class);
888         IBinder mockBinder = mock(IBinder.class);
889         when(mockHandler.asBinder()).thenReturn(mockBinder);
890         doThrow(new ServiceSpecificException(0)).when(mHalService).subscribeProperty(any());
891 
892         List<CarSubscription> subscribeOptions = List.of(
893                 createCarSubscriptionOption(SPEED_ID, new int[]{0}, 20f),
894                 createCarSubscriptionOption(HVAC_TEMP, new int[]{0}, 0f));
895 
896         assertThrows(ServiceSpecificException.class, () ->
897                 mService.registerListener(subscribeOptions, mockHandler));
898     }
899 
900     @Test
testRegisterListenerWithSubscription_exceptionFromPropertyHalService_retry()901     public void testRegisterListenerWithSubscription_exceptionFromPropertyHalService_retry()
902             throws Exception {
903         ICarPropertyEventListener mockHandler = mock(ICarPropertyEventListener.class);
904         IBinder mockBinder = mock(IBinder.class);
905         when(mockHandler.asBinder()).thenReturn(mockBinder);
906         doThrow(new ServiceSpecificException(0)).when(mHalService).subscribeProperty(any());
907         CarPropertyValue mockValue = mock(CarPropertyValue.class);
908         when(mHalService.getProperty(anyInt(), anyInt())).thenReturn(mockValue);
909 
910         List<CarSubscription> subscribeOptions = List.of(
911                 createCarSubscriptionOption(SPEED_ID, new int[]{0}, 20f),
912                 createCarSubscriptionOption(HVAC_TEMP, new int[]{0}, 0f));
913 
914         assertThrows(ServiceSpecificException.class, () ->
915                 mService.registerListener(subscribeOptions, mockHandler));
916 
917         // Finish the async get initial value task.
918         mService.finishHandlerTasks(/*timeoutInMs=*/ 1000);
919 
920         // Simulate the error goes away.
921         clearInvocations(mHalService);
922         doNothing().when(mHalService).subscribeProperty(any());
923 
924         mService.registerListener(subscribeOptions, mockHandler);
925 
926         // Verify that the retry request must go to PropertyHalService.
927         verify(mHalService).subscribeProperty(subscribeOptions);
928     }
929 
930     @Test
testregisterListener_alreadySubscribedOptionFilteredOut()931     public void testregisterListener_alreadySubscribedOptionFilteredOut() {
932         ICarPropertyEventListener mockHandler1 = mock(ICarPropertyEventListener.class);
933         ICarPropertyEventListener mockHandler2 = mock(ICarPropertyEventListener.class);
934         IBinder mockBinder1 = mock(IBinder.class);
935         IBinder mockBinder2 = mock(IBinder.class);
936         when(mockHandler1.asBinder()).thenReturn(mockBinder1);
937         when(mockHandler2.asBinder()).thenReturn(mockBinder2);
938         CarPropertyValue mockValue = mock(CarPropertyValue.class);
939         when(mHalService.getProperty(anyInt(), anyInt())).thenReturn(mockValue);
940 
941         // Client 1
942         // On-change:
943         // [left -> 0f]
944         // Continuous:
945         // [left -> 20f, right -> 20f]
946         List<CarSubscription> firstSubscribeOptions = List.of(
947                 createCarSubscriptionOption(ON_CHANGE_ZONED_PROPERTY_ID, new int[]{
948                         VehicleAreaWindow.WINDOW_ROW_1_LEFT}, 0f),
949                 createCarSubscriptionOption(CONTINUOUS_ZONED_PROPERTY_ID, new int[]{
950                         VehicleAreaWindow.WINDOW_ROW_1_LEFT, VehicleAreaWindow.WINDOW_ROW_1_RIGHT},
951                         20f));
952         mService.registerListener(firstSubscribeOptions, mockHandler1);
953 
954         verify(mHalService).subscribeProperty(firstSubscribeOptions);
955 
956         // Client 2
957         // On-change:
958         // [left -> 0f (filtered out), right -> 0f]
959         // Continuous:
960         // [left -> 10f (filtered out), right -> 30f]
961         //
962         // The request received by PropertyHalService should be:
963         // On-change:
964         // [right -> 0f]
965         // Continuous:
966         // [right -> 30f]
967         List<CarSubscription> secondSubscribeOptions = List.of(
968                 createCarSubscriptionOption(ON_CHANGE_ZONED_PROPERTY_ID, new int[]{
969                         VehicleAreaWindow.WINDOW_ROW_1_LEFT, VehicleAreaWindow.WINDOW_ROW_1_RIGHT},
970                         0f),
971                 createCarSubscriptionOption(CONTINUOUS_ZONED_PROPERTY_ID, new int[]{
972                         VehicleAreaWindow.WINDOW_ROW_1_LEFT}, 10f),
973                 createCarSubscriptionOption(CONTINUOUS_ZONED_PROPERTY_ID, new int[]{
974                         VehicleAreaWindow.WINDOW_ROW_1_RIGHT}, 30f));
975         mService.registerListener(secondSubscribeOptions, mockHandler2);
976 
977         verify(mHalService).subscribeProperty(List.of(
978                 createCarSubscriptionOption(ON_CHANGE_ZONED_PROPERTY_ID, new int[]{
979                         VehicleAreaWindow.WINDOW_ROW_1_RIGHT}, 0f),
980                 createCarSubscriptionOption(CONTINUOUS_ZONED_PROPERTY_ID, new int[]{
981                         VehicleAreaWindow.WINDOW_ROW_1_RIGHT}, 30f)
982         ));
983 
984         clearInvocations(mHalService);
985 
986         // Unregister client 1 for on-change [left -> 0f]
987         // Since client2 still registered for on-change [left -> 0f], nothing changes.
988         mService.unregisterListener(ON_CHANGE_ZONED_PROPERTY_ID, mockHandler1);
989 
990         verify(mHalService, never()).subscribeProperty(any());
991         verify(mHalService, never()).unsubscribeProperty(anyInt());
992 
993         // Unregister client 1 for continuous property, left should be 10 because client 2 registers
994         // to left at 10. Right should still be subscribed at 30 and no change.
995         mService.unregisterListener(CONTINUOUS_ZONED_PROPERTY_ID, mockHandler1);
996 
997         verify(mHalService).subscribeProperty(List.of(
998                 createCarSubscriptionOption(CONTINUOUS_ZONED_PROPERTY_ID, new int[]{
999                         VehicleAreaWindow.WINDOW_ROW_1_LEFT}, 10f)));
1000 
1001         // Unregister client 2 for on-change property, should cause unsubscribe for the property.
1002         mService.unregisterListener(ON_CHANGE_ZONED_PROPERTY_ID, mockHandler2);
1003 
1004         verify(mHalService).unsubscribeProperty(ON_CHANGE_ZONED_PROPERTY_ID);
1005 
1006         // Unregister client 2 for continuous property, should cause unsubscribe for the property.
1007         clearInvocations(mHalService);
1008         mService.unregisterListener(CONTINUOUS_ZONED_PROPERTY_ID, mockHandler2);
1009 
1010         verify(mHalService).unsubscribeProperty(CONTINUOUS_ZONED_PROPERTY_ID);
1011     }
1012 
1013     @Test
testRegisterListenerWithSubscription_unregisterListener()1014     public void testRegisterListenerWithSubscription_unregisterListener() {
1015         ICarPropertyEventListener mockHandler1 = mock(ICarPropertyEventListener.class);
1016         ICarPropertyEventListener mockHandler2 = mock(ICarPropertyEventListener.class);
1017         IBinder mockBinder1 = mock(IBinder.class);
1018         IBinder mockBinder2 = mock(IBinder.class);
1019         when(mockHandler1.asBinder()).thenReturn(mockBinder1);
1020         when(mockHandler2.asBinder()).thenReturn(mockBinder2);
1021         CarPropertyValue mockValue = mock(CarPropertyValue.class);
1022         when(mHalService.getProperty(anyInt(), anyInt())).thenReturn(mockValue);
1023 
1024         // Client 1
1025         // On-change:
1026         // [left -> 0f]
1027         // Continuous:
1028         // [left -> 20f, right -> 20f]
1029         List<CarSubscription> firstSubscribeOptions = List.of(
1030                 createCarSubscriptionOption(ON_CHANGE_ZONED_PROPERTY_ID, new int[]{
1031                         VehicleAreaWindow.WINDOW_ROW_1_LEFT}, 0f),
1032                 createCarSubscriptionOption(CONTINUOUS_ZONED_PROPERTY_ID, new int[]{
1033                         VehicleAreaWindow.WINDOW_ROW_1_LEFT, VehicleAreaWindow.WINDOW_ROW_1_RIGHT},
1034                         20f));
1035         mService.registerListener(firstSubscribeOptions, mockHandler1);
1036 
1037         // Client 2
1038         // On-change:
1039         // [left -> 0f (filtered out), right -> 0f]
1040         // Continuous:
1041         // [left -> 10f (filtered out), right -> 30f]
1042         List<CarSubscription> secondSubscribeOptions = List.of(
1043                 createCarSubscriptionOption(ON_CHANGE_ZONED_PROPERTY_ID, new int[]{
1044                         VehicleAreaWindow.WINDOW_ROW_1_LEFT, VehicleAreaWindow.WINDOW_ROW_1_RIGHT},
1045                         0f),
1046                 createCarSubscriptionOption(CONTINUOUS_ZONED_PROPERTY_ID, new int[]{
1047                         VehicleAreaWindow.WINDOW_ROW_1_LEFT}, 10f),
1048                 createCarSubscriptionOption(CONTINUOUS_ZONED_PROPERTY_ID, new int[]{
1049                         VehicleAreaWindow.WINDOW_ROW_1_RIGHT}, 30f));
1050         mService.registerListener(secondSubscribeOptions, mockHandler2);
1051 
1052         clearInvocations(mHalService);
1053 
1054         // Unregister client 2 for on-change. This should cause right to be unsubscribed. However,
1055         // we do not have unsubscribe API for zone, so we will do nothing.
1056         mService.unregisterListener(ON_CHANGE_ZONED_PROPERTY_ID, mockHandler2);
1057 
1058         verify(mHalService, never()).subscribeProperty(any());
1059         verify(mHalService, never()).unsubscribeProperty(anyInt());
1060 
1061         // Unregister client 1 for on-change, now no client is registered to on-change.
1062         mService.unregisterListener(ON_CHANGE_ZONED_PROPERTY_ID, mockHandler1);
1063 
1064         verify(mHalService).unsubscribeProperty(ON_CHANGE_ZONED_PROPERTY_ID);
1065 
1066         // Unregister client 2 for continuous. This should update right from 30f to 20f.
1067         clearInvocations(mHalService);
1068         mService.unregisterListener(CONTINUOUS_ZONED_PROPERTY_ID, mockHandler2);
1069 
1070         verify(mHalService).subscribeProperty(List.of(
1071                 createCarSubscriptionOption(CONTINUOUS_ZONED_PROPERTY_ID, new int[]{
1072                         VehicleAreaWindow.WINDOW_ROW_1_RIGHT}, 20f)));
1073         verify(mHalService, never()).unsubscribeProperty(anyInt());
1074 
1075         clearInvocations(mHalService);
1076         // Unregister client 1 for on-change, now no client is registered to continuous.
1077         mService.unregisterListener(CONTINUOUS_ZONED_PROPERTY_ID, mockHandler1);
1078 
1079         verify(mHalService, never()).subscribeProperty(any());
1080         verify(mHalService).unsubscribeProperty(CONTINUOUS_ZONED_PROPERTY_ID);
1081     }
1082 
1083     @Test
testregisterListener_emptyAreaIds()1084     public void testregisterListener_emptyAreaIds() throws Exception {
1085         ICarPropertyEventListener mockHandler = mock(ICarPropertyEventListener.class);
1086         IBinder mockBinder = mock(IBinder.class);
1087         when(mockHandler.asBinder()).thenReturn(mockBinder);
1088 
1089         assertThrows(IllegalArgumentException.class, () ->
1090                 mService.registerListener(List.of(
1091                         createCarSubscriptionOption(ON_CHANGE_ZONED_PROPERTY_ID, new int[]{}, 0f)),
1092                 mockHandler));
1093     }
1094 
1095     @Test
testregisterListener_nullAreaIds()1096     public void testregisterListener_nullAreaIds() throws Exception {
1097         ICarPropertyEventListener mockHandler = mock(ICarPropertyEventListener.class);
1098         IBinder mockBinder = mock(IBinder.class);
1099         when(mockHandler.asBinder()).thenReturn(mockBinder);
1100 
1101         assertThrows(IllegalArgumentException.class, () ->
1102                 mService.registerListener(List.of(
1103                         createCarSubscriptionOption(ON_CHANGE_ZONED_PROPERTY_ID,
1104                                 /* areaId= */ null, 0f)),
1105                 mockHandler));
1106     }
1107 
1108     @Test
testUnregisterListener_exceptionFromPropertyHalService()1109     public void testUnregisterListener_exceptionFromPropertyHalService()
1110             throws Exception {
1111         ICarPropertyEventListener mockHandler = mock(ICarPropertyEventListener.class);
1112         IBinder mockBinder = mock(IBinder.class);
1113         when(mockHandler.asBinder()).thenReturn(mockBinder);
1114         CarPropertyValue mockValue = mock(CarPropertyValue.class);
1115         when(mHalService.getProperty(anyInt(), anyInt())).thenReturn(mockValue);
1116         doThrow(new ServiceSpecificException(0)).when(mHalService).unsubscribeProperty(anyInt());
1117 
1118         List<CarSubscription> subscribeOptions = List.of(
1119                 createCarSubscriptionOption(SPEED_ID, new int[]{0}, 20f),
1120                 createCarSubscriptionOption(HVAC_TEMP, new int[]{0}, 0f));
1121 
1122         mService.registerListener(subscribeOptions, mockHandler);
1123 
1124         assertThrows(ServiceSpecificException.class, () ->
1125                 mService.unregisterListener(SPEED_ID, mockHandler));
1126     }
1127 
1128     @Test
testUnregisterListener_exceptionFromPropertyHalService_retry()1129     public void testUnregisterListener_exceptionFromPropertyHalService_retry()
1130             throws Exception {
1131         ICarPropertyEventListener mockHandler = mock(ICarPropertyEventListener.class);
1132         IBinder mockBinder = mock(IBinder.class);
1133         when(mockHandler.asBinder()).thenReturn(mockBinder);
1134         CarPropertyValue mockValue = mock(CarPropertyValue.class);
1135         when(mHalService.getProperty(anyInt(), anyInt())).thenReturn(mockValue);
1136         doThrow(new ServiceSpecificException(0)).when(mHalService).unsubscribeProperty(anyInt());
1137 
1138         List<CarSubscription> subscribeOptions = List.of(
1139                 createCarSubscriptionOption(SPEED_ID, new int[]{0}, 20f),
1140                 createCarSubscriptionOption(HVAC_TEMP, new int[]{0}, 0f));
1141 
1142         mService.registerListener(subscribeOptions, mockHandler);
1143 
1144         assertThrows(ServiceSpecificException.class, () ->
1145                 mService.unregisterListener(SPEED_ID, mockHandler));
1146 
1147         // Finish the async get initial value task.
1148         mService.finishHandlerTasks(/*timeoutInMs=*/ 1000);
1149 
1150         // Simulate the error goes away.
1151         clearInvocations(mHalService);
1152         doNothing().when(mHalService).unsubscribeProperty(anyInt());
1153 
1154         mService.unregisterListener(SPEED_ID, mockHandler);
1155 
1156         // The retry request must go to PropertyHalService.
1157         verify(mHalService).unsubscribeProperty(SPEED_ID);
1158     }
1159 
1160     @Test
testOnPropertySetError()1161     public void testOnPropertySetError() throws Exception {
1162         int areaId = 0;
1163         int propertyId = WRITE_ONLY_INT_PROPERTY_ID;
1164         CarPropertyValue<Integer> value = new CarPropertyValue<>(
1165                 propertyId, areaId, MIN_INT_VALUE);
1166         ICarPropertyEventListener mockEventListener = mock(ICarPropertyEventListener.class);
1167         IBinder mBinder = mock(IBinder.class);
1168         when(mockEventListener.asBinder()).thenReturn(mBinder);
1169         mService.setProperty(value, mockEventListener);
1170 
1171         int errorCode = 2;
1172         mService.onPropertySetError(propertyId, areaId, errorCode);
1173 
1174         verify(mockEventListener).onEvent(mPropertyEventCaptor.capture());
1175 
1176         List<CarPropertyEvent> events = mPropertyEventCaptor.getValue();
1177         assertThat(events).containsExactly(CarPropertyEvent.createErrorEventWithErrorCode(
1178                 propertyId, areaId, errorCode
1179         ));
1180     }
1181 
1182     @Test
testOnPropertySetError_unregisterProperty()1183     public void testOnPropertySetError_unregisterProperty() throws Exception {
1184         int areaId = 0;
1185         int propertyId = WRITE_ONLY_INT_PROPERTY_ID;
1186         CarPropertyValue<Integer> value = new CarPropertyValue<>(
1187                 propertyId, areaId, MIN_INT_VALUE);
1188         ICarPropertyEventListener mockEventListener = mock(ICarPropertyEventListener.class);
1189         IBinder mBinder = mock(IBinder.class);
1190         when(mockEventListener.asBinder()).thenReturn(mBinder);
1191         mService.setProperty(value, mockEventListener);
1192 
1193         int errorCode = 2;
1194         mService.onPropertySetError(propertyId, areaId, errorCode);
1195 
1196         verify(mockEventListener).onEvent(any());
1197         clearInvocations(mockEventListener);
1198 
1199         // After unregisterListener, the listener must not receive onPropertyError event.
1200         mService.unregisterListener(propertyId, mockEventListener);
1201         mService.onPropertySetError(propertyId, areaId, errorCode);
1202 
1203         verify(mockEventListener, never()).onEvent(any());
1204     }
1205 
1206     private static class EventListener extends ICarPropertyEventListener.Stub{
1207         private final CarPropertyService mService;
1208         private List<CarPropertyEvent> mEvents;
1209         private Exception mException;
1210 
EventListener(CarPropertyService service)1211         EventListener(CarPropertyService service) {
1212             mService = service;
1213         }
1214 
1215         @Override
onEvent(List<CarPropertyEvent> events)1216         public void onEvent(List<CarPropertyEvent> events) throws RemoteException {
1217             CountDownLatch latch = new CountDownLatch(1);
1218             new Thread(() -> {
1219                 // Call a function in CarPropertyService to make sure there is no dead lock. This
1220                 // call doesn't actually do anything. We must use a new thread here because
1221                 // the same thread can obtain the same lock again even if there is a dead lock.
1222                 mService.getReadPermission(0);
1223                 latch.countDown();
1224             }, "onEventThread").start();
1225             try {
1226                 if (!latch.await(5, TimeUnit.SECONDS)) {
1227                     mException = new Exception("timeout waiting for getReadPermission, dead lock?");
1228                     return;
1229                 }
1230             } catch (InterruptedException e) {
1231                 Thread.currentThread().interrupt();
1232                 mException = new Exception("waiting for onEvent thread interrupted");
1233                 return;
1234             }
1235             mEvents = events;
1236         }
1237 
getEvents()1238         List<CarPropertyEvent> getEvents() throws Exception {
1239             if (mException != null) {
1240                 throw mException;
1241             }
1242             return mEvents;
1243         }
1244     }
1245 
1246     @Test
testOnEventCallback_MustNotHaveDeadLock()1247     public void testOnEventCallback_MustNotHaveDeadLock() throws Exception {
1248         // This test checks that CarPropertyService must not hold any lock while calling
1249         // ICarPropertyListener's onEvent callback, otherwise it might cause dead lock if
1250         // the callback calls another function in CarPropertyService that requires the same lock.
1251         mService.init();
1252 
1253         CarPropertyValue<Float> value = new CarPropertyValue<Float>(HVAC_TEMP, 0, 1.0f);
1254         when(mHalService.getProperty(HVAC_TEMP, 0)).thenReturn(value);
1255         EventListener listener = new EventListener(mService);
1256 
1257         mService.registerListener(HVAC_TEMP, /* updateRateHz= */ SENSOR_RATE_ONCHANGE, listener);
1258         List<CarPropertyEvent> events = List.of(new CarPropertyEvent(0, value));
1259         mService.onPropertyChange(events);
1260 
1261         List<CarPropertyEvent> actualEvents = listener.getEvents();
1262 
1263         assertThat(actualEvents.size()).isEqualTo(events.size());
1264         for (int i = 0; i < events.size(); i++) {
1265             CarPropertyEvent actualEvent = actualEvents.get(i);
1266             CarPropertyEvent expectedEvent = events.get(i);
1267             assertThat(actualEvent.getEventType()).isEqualTo(expectedEvent.getEventType());
1268             assertThat(actualEvent.getErrorCode()).isEqualTo(expectedEvent.getErrorCode());
1269             CarPropertyValue actualCarPropertyValue = actualEvent.getCarPropertyValue();
1270             CarPropertyValue expectedCarPropertyValue = expectedEvent.getCarPropertyValue();
1271             assertThat(actualCarPropertyValue.getPropertyId()).isEqualTo(
1272                     expectedCarPropertyValue.getPropertyId());
1273             assertThat(actualCarPropertyValue.getAreaId()).isEqualTo(
1274                     expectedCarPropertyValue.getAreaId());
1275             assertThat(actualCarPropertyValue.getStatus()).isEqualTo(
1276                     expectedCarPropertyValue.getStatus());
1277             assertThat(actualCarPropertyValue.getTimestamp()).isEqualTo(
1278                     expectedCarPropertyValue.getTimestamp());
1279             assertThat(actualCarPropertyValue.getValue()).isEqualTo(
1280                     expectedCarPropertyValue.getValue());
1281         }
1282     }
1283 
1284     @Test
getProperty_throwsExceptionBecauseOfUnsupportedPropertyId()1285     public void getProperty_throwsExceptionBecauseOfUnsupportedPropertyId() {
1286         assertThrows(IllegalArgumentException.class,
1287                 () -> mService.getProperty(VehiclePropertyIds.INVALID,
1288                         VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL));
1289     }
1290 
1291     @Test
getProperty_throwsExceptionBecausePropertyIsNotReadable()1292     public void getProperty_throwsExceptionBecausePropertyIsNotReadable() {
1293         assertThrows(IllegalArgumentException.class,
1294                 () -> mService.getProperty(WRITE_ONLY_INT_PROPERTY_ID,
1295                         VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL));
1296     }
1297 
1298     @Test
getProperty_throwsSecurityExceptionIfPlatformDoesNotHavePermissionToRead()1299     public void getProperty_throwsSecurityExceptionIfPlatformDoesNotHavePermissionToRead() {
1300         assertThrows(SecurityException.class,
1301                 () -> mService.getProperty(VehiclePropertyIds.GEAR_SELECTION, 0));
1302     }
1303 
1304     @Test
getProperty_throwsSecurityExceptionIfAppDoesNotHavePermissionToRead()1305     public void getProperty_throwsSecurityExceptionIfAppDoesNotHavePermissionToRead() {
1306         when(mHalService.isReadable(mContext, VehiclePropertyIds.GEAR_SELECTION))
1307                 .thenReturn(false);
1308         assertThrows(SecurityException.class,
1309                 () -> mService.getProperty(VehiclePropertyIds.GEAR_SELECTION, 0));
1310     }
1311 
1312     @Test
getProperty_throwsExceptionBecauseOfUnsupportedAreaId()1313     public void getProperty_throwsExceptionBecauseOfUnsupportedAreaId() {
1314         assertThrows(IllegalArgumentException.class,
1315                 () -> mService.getProperty(ON_CHANGE_READ_WRITE_PROPERTY_ID,
1316                         NOT_SUPPORTED_AREA_ID));
1317     }
1318 
1319     @Test
setProperty_throwsExceptionBecauseOfNullCarPropertyValue()1320     public void setProperty_throwsExceptionBecauseOfNullCarPropertyValue() {
1321         assertThrows(NullPointerException.class,
1322                 () -> mService.setProperty(null, mICarPropertyEventListener));
1323     }
1324 
1325     @Test
setProperty_throwsExceptionBecauseOfNullListener()1326     public void setProperty_throwsExceptionBecauseOfNullListener() {
1327         assertThrows(NullPointerException.class, () -> mService.setProperty(
1328                 new CarPropertyValue(ON_CHANGE_READ_WRITE_PROPERTY_ID, GLOBAL_AREA_ID,
1329                         Integer.MAX_VALUE), null));
1330     }
1331 
1332     @Test
setProperty_throwsExceptionBecauseOfUnsupportedPropertyId()1333     public void setProperty_throwsExceptionBecauseOfUnsupportedPropertyId() {
1334         assertThrows(IllegalArgumentException.class, () -> mService.setProperty(
1335                 new CarPropertyValue(VehiclePropertyIds.INVALID, GLOBAL_AREA_ID, Integer.MAX_VALUE),
1336                 mICarPropertyEventListener));
1337     }
1338 
1339     @Test
setProperty_throwsExceptionBecausePropertyIsNotWritable()1340     public void setProperty_throwsExceptionBecausePropertyIsNotWritable() {
1341         assertThrows(IllegalArgumentException.class, () -> mService.setProperty(
1342                 new CarPropertyValue(CONTINUOUS_READ_ONLY_PROPERTY_ID, GLOBAL_AREA_ID,
1343                         Integer.MAX_VALUE), mICarPropertyEventListener));
1344     }
1345 
1346     @Test
setProperty_throwsSecurityExceptionIfPlatformDoesNotHavePermissionToWrite()1347     public void setProperty_throwsSecurityExceptionIfPlatformDoesNotHavePermissionToWrite() {
1348         assertThrows(SecurityException.class, () -> mService.setProperty(
1349                 new CarPropertyValue(NO_PERMISSION_PROPERTY_ID, GLOBAL_AREA_ID, Integer.MAX_VALUE),
1350                 mICarPropertyEventListener));
1351     }
1352 
1353     @Test
setProperty_throwsSecurityExceptionIfAppDoesNotHavePermissionToWrite()1354     public void setProperty_throwsSecurityExceptionIfAppDoesNotHavePermissionToWrite() {
1355         when(mHalService.isWritable(mContext, NO_PERMISSION_PROPERTY_ID))
1356                 .thenReturn(false);
1357         assertThrows(SecurityException.class, () -> mService.setProperty(
1358                 new CarPropertyValue(NO_PERMISSION_PROPERTY_ID, GLOBAL_AREA_ID, Integer.MAX_VALUE),
1359                 mICarPropertyEventListener));
1360     }
1361 
1362     @Test
setProperty_throwsExceptionBecauseOfUnsupportedAreaId()1363     public void setProperty_throwsExceptionBecauseOfUnsupportedAreaId() {
1364         assertThrows(IllegalArgumentException.class, () -> mService.setProperty(
1365                 new CarPropertyValue(ON_CHANGE_READ_WRITE_PROPERTY_ID, NOT_SUPPORTED_AREA_ID,
1366                         Integer.MAX_VALUE), mICarPropertyEventListener));
1367     }
1368 
1369     @Test
setProperty_throwsExceptionBecauseOfNullSetValue()1370     public void setProperty_throwsExceptionBecauseOfNullSetValue() {
1371         assertThrows(IllegalArgumentException.class, () -> mService.setProperty(
1372                 new CarPropertyValue(ON_CHANGE_READ_WRITE_PROPERTY_ID, GLOBAL_AREA_ID, null),
1373                 mICarPropertyEventListener));
1374     }
1375 
1376     @Test
setProperty_throwsExceptionBecauseOfSetValueTypeMismatch()1377     public void setProperty_throwsExceptionBecauseOfSetValueTypeMismatch() {
1378         assertThrows(IllegalArgumentException.class, () -> mService.setProperty(
1379                 new CarPropertyValue(WRITE_ONLY_INT_PROPERTY_ID, GLOBAL_AREA_ID, Float.MAX_VALUE),
1380                 mICarPropertyEventListener));
1381     }
1382 
1383     @Test
setProperty_throwsExceptionBecauseOfIntSetValueIsLessThanMinValue()1384     public void setProperty_throwsExceptionBecauseOfIntSetValueIsLessThanMinValue() {
1385         assertThrows(IllegalArgumentException.class, () -> mService.setProperty(
1386                 new CarPropertyValue(WRITE_ONLY_INT_PROPERTY_ID, GLOBAL_AREA_ID, MIN_INT_VALUE - 1),
1387                 mICarPropertyEventListener));
1388     }
1389 
1390     @Test
setProperty_throwsExceptionBecauseOfIntSetValueIsGreaterThanMaxValue()1391     public void setProperty_throwsExceptionBecauseOfIntSetValueIsGreaterThanMaxValue() {
1392         assertThrows(IllegalArgumentException.class, () -> mService.setProperty(
1393                 new CarPropertyValue(WRITE_ONLY_INT_PROPERTY_ID, GLOBAL_AREA_ID, MAX_INT_VALUE + 1),
1394                 mICarPropertyEventListener));
1395     }
1396 
1397     @Test
setProperty_throwsExceptionBecauseOfLongSetValueIsLessThanMinValue()1398     public void setProperty_throwsExceptionBecauseOfLongSetValueIsLessThanMinValue() {
1399         assertThrows(IllegalArgumentException.class, () -> mService.setProperty(
1400                 new CarPropertyValue(WRITE_ONLY_LONG_PROPERTY_ID, GLOBAL_AREA_ID,
1401                         MIN_LONG_VALUE - 1), mICarPropertyEventListener));
1402     }
1403 
1404     @Test
setProperty_throwsExceptionBecauseOfLongSetValueIsGreaterThanMaxValue()1405     public void setProperty_throwsExceptionBecauseOfLongSetValueIsGreaterThanMaxValue() {
1406         assertThrows(IllegalArgumentException.class, () -> mService.setProperty(
1407                 new CarPropertyValue(WRITE_ONLY_LONG_PROPERTY_ID, GLOBAL_AREA_ID,
1408                         MAX_LONG_VALUE + 1), mICarPropertyEventListener));
1409     }
1410 
1411     @Test
setProperty_throwsExceptionBecauseOfFloatSetValueIsLessThanMinValue()1412     public void setProperty_throwsExceptionBecauseOfFloatSetValueIsLessThanMinValue() {
1413         assertThrows(IllegalArgumentException.class, () -> mService.setProperty(
1414                 new CarPropertyValue(WRITE_ONLY_LONG_PROPERTY_ID, GLOBAL_AREA_ID,
1415                         MIN_LONG_VALUE - 1), mICarPropertyEventListener));
1416     }
1417 
1418     @Test
setProperty_throwsExceptionBecauseOfFloatSetValueIsGreaterThanMaxValue()1419     public void setProperty_throwsExceptionBecauseOfFloatSetValueIsGreaterThanMaxValue() {
1420         assertThrows(IllegalArgumentException.class, () -> mService.setProperty(
1421                 new CarPropertyValue(WRITE_ONLY_FLOAT_PROPERTY_ID, GLOBAL_AREA_ID,
1422                         MAX_FLOAT_VALUE + 1), mICarPropertyEventListener));
1423     }
1424 
1425     @Test
setProperty_throwsExceptionBecauseOfSetValueIsNotInSupportedEnumValues()1426     public void setProperty_throwsExceptionBecauseOfSetValueIsNotInSupportedEnumValues() {
1427         assertThrows(IllegalArgumentException.class, () -> mService.setProperty(
1428                 new CarPropertyValue(WRITE_ONLY_ENUM_PROPERTY_ID, GLOBAL_AREA_ID,
1429                         UNSUPPORTED_ENUM_VALUE), mICarPropertyEventListener));
1430     }
1431 
1432     @Test
setProperty_throwsExceptionBecauseOfSetValueIsErrorStateEnum()1433     public void setProperty_throwsExceptionBecauseOfSetValueIsErrorStateEnum() {
1434         assertThrows(IllegalArgumentException.class, () -> mService.setProperty(
1435                 new CarPropertyValue(WRITE_ONLY_OTHER_ENUM_PROPERTY_ID, GLOBAL_AREA_ID,
1436                         SUPPORTED_ERROR_STATE_ENUM_VALUE), mICarPropertyEventListener));
1437     }
1438 
1439     @Test
setProperty_throwsExceptionBecauseOfSetValueIsOtherStateEnum()1440     public void setProperty_throwsExceptionBecauseOfSetValueIsOtherStateEnum() {
1441         assertThrows(IllegalArgumentException.class, () -> mService.setProperty(
1442                 new CarPropertyValue(WRITE_ONLY_OTHER_ENUM_PROPERTY_ID, GLOBAL_AREA_ID,
1443                         SUPPORTED_OTHER_STATE_ENUM_VALUE), mICarPropertyEventListener));
1444     }
1445 
1446     @Test
registerListener_throwsExceptionBecauseOfNullListener()1447     public void registerListener_throwsExceptionBecauseOfNullListener() {
1448         assertThrows(NullPointerException.class,
1449                 () -> mService.registerListener(ON_CHANGE_READ_WRITE_PROPERTY_ID,
1450                         CarPropertyManager.SENSOR_RATE_NORMAL,
1451                         /* carPropertyEventListener= */ null));
1452     }
1453 
1454     @Test
registerListener_throwsExceptionBecauseOfUnsupportedPropertyId()1455     public void registerListener_throwsExceptionBecauseOfUnsupportedPropertyId() {
1456         assertThrows(IllegalArgumentException.class,
1457                 () -> mService.registerListener(VehiclePropertyIds.INVALID,
1458                         CarPropertyManager.SENSOR_RATE_NORMAL, mICarPropertyEventListener));
1459     }
1460 
1461     @Test
registerListener_throwsExceptionBecausePropertyIsNotReadable()1462     public void registerListener_throwsExceptionBecausePropertyIsNotReadable() {
1463         assertThrows(IllegalArgumentException.class,
1464                 () -> mService.registerListener(WRITE_ONLY_INT_PROPERTY_ID,
1465                         CarPropertyManager.SENSOR_RATE_NORMAL, mICarPropertyEventListener));
1466     }
1467 
1468     @Test
registerListener_throwsSecurityExceptionIfPlatformDoesNotHavePermissionToRead()1469     public void registerListener_throwsSecurityExceptionIfPlatformDoesNotHavePermissionToRead() {
1470         assertThrows(SecurityException.class,
1471                 () -> mService.registerListener(NO_PERMISSION_PROPERTY_ID, 0,
1472                         mICarPropertyEventListener));
1473     }
1474 
1475     @Test
registerListener_throwsSecurityExceptionIfAppDoesNotHavePermissionToRead()1476     public void registerListener_throwsSecurityExceptionIfAppDoesNotHavePermissionToRead() {
1477         when(mHalService.isReadable(mContext, NO_PERMISSION_PROPERTY_ID))
1478                 .thenReturn(false);
1479         assertThrows(SecurityException.class,
1480                 () -> mService.registerListener(NO_PERMISSION_PROPERTY_ID, 0,
1481                         mICarPropertyEventListener));
1482     }
1483 
1484     @Test
registerListener_updatesRateForNonContinuousProperty()1485     public void registerListener_updatesRateForNonContinuousProperty() {
1486         mService.registerListener(ON_CHANGE_READ_WRITE_PROPERTY_ID,
1487                 CarPropertyManager.SENSOR_RATE_FAST, mICarPropertyEventListener);
1488         verify(mHalService).subscribeProperty(List.of(createCarSubscriptionOption(
1489                 ON_CHANGE_READ_WRITE_PROPERTY_ID, new int[]{0}, SENSOR_RATE_ONCHANGE)));
1490     }
1491 
1492     @Test
registerListener_VurDefaultOn()1493     public void registerListener_VurDefaultOn() {
1494         mService.registerListener(CONTINUOUS_READ_ONLY_PROPERTY_ID, MIN_SAMPLE_RATE,
1495                 mICarPropertyEventListener);
1496 
1497         verify(mHalService).subscribeProperty(List.of(createCarSubscriptionOption(
1498                 CONTINUOUS_READ_ONLY_PROPERTY_ID, new int[]{0}, MIN_SAMPLE_RATE,
1499                 /* enableVur= */ true, /* resolution */ 0.0f)));
1500     }
1501 
1502     @Test
registerListener_VurExplicitOn()1503     public void registerListener_VurExplicitOn() {
1504         mService.registerListener(CONTINUOUS_READ_ONLY_PROPERTY_ID, MIN_SAMPLE_RATE,
1505                 /* enableVariableUpdateRate= */ true,
1506                 mICarPropertyEventListener);
1507 
1508         verify(mHalService).subscribeProperty(List.of(createCarSubscriptionOption(
1509                 CONTINUOUS_READ_ONLY_PROPERTY_ID, new int[]{0}, MIN_SAMPLE_RATE,
1510                 /* enableVur= */ true, /* resolution */ 0.0f)));
1511     }
1512 
1513     @Test
registerListener_VurExplicitOff()1514     public void registerListener_VurExplicitOff() {
1515         mService.registerListener(CONTINUOUS_READ_ONLY_PROPERTY_ID, MIN_SAMPLE_RATE,
1516                 /* enableVariableUpdateRate= */ false,
1517                 mICarPropertyEventListener);
1518 
1519         verify(mHalService).subscribeProperty(List.of(createCarSubscriptionOption(
1520                 CONTINUOUS_READ_ONLY_PROPERTY_ID, new int[]{0}, MIN_SAMPLE_RATE,
1521                 /* enableVur= */ false, /* resolution */ 0.0f)));
1522     }
1523 
1524     @Test
registerListener_VurFalseWhenFeatureOff()1525     public void registerListener_VurFalseWhenFeatureOff() {
1526         when(mFeatureFlags.variableUpdateRate()).thenReturn(false);
1527 
1528         mService.registerListener(CONTINUOUS_READ_ONLY_PROPERTY_ID, MIN_SAMPLE_RATE,
1529                 mICarPropertyEventListener);
1530 
1531         verify(mHalService).subscribeProperty(List.of(createCarSubscriptionOption(
1532                 CONTINUOUS_READ_ONLY_PROPERTY_ID, new int[]{0}, MIN_SAMPLE_RATE,
1533                 /* enableVur= */ false, /* resolution */ 0.0f)));
1534     }
1535 
1536     @Test
registerListener_Resolution()1537     public void registerListener_Resolution() {
1538         mService.registerListener(CONTINUOUS_READ_ONLY_PROPERTY_ID, MIN_SAMPLE_RATE,
1539                 /* enableVariableUpdateRate= */ true, /* resolution */ 1.0f,
1540                 mICarPropertyEventListener);
1541 
1542         verify(mHalService).subscribeProperty(List.of(createCarSubscriptionOption(
1543                 CONTINUOUS_READ_ONLY_PROPERTY_ID, new int[]{0}, MIN_SAMPLE_RATE,
1544                 /* enableVur= */ true, /* resolution */ 1.0f)));
1545     }
1546 
1547     @Test
registerListener_ResolutionZeroWhenFeatureOff()1548     public void registerListener_ResolutionZeroWhenFeatureOff() {
1549         when(mFeatureFlags.subscriptionWithResolution()).thenReturn(false);
1550 
1551         mService.registerListener(CONTINUOUS_READ_ONLY_PROPERTY_ID, MIN_SAMPLE_RATE,
1552                 /* enableVariableUpdateRate= */ true, /* resolution */ 1.0f,
1553                 mICarPropertyEventListener);
1554 
1555         verify(mHalService).subscribeProperty(List.of(createCarSubscriptionOption(
1556                 CONTINUOUS_READ_ONLY_PROPERTY_ID, new int[]{0}, MIN_SAMPLE_RATE,
1557                 /* enableVur= */ true, /* resolution */ 0.0f)));
1558     }
1559 
1560     @Test
registerListener_updatesRateToMinForContinuousProperty()1561     public void registerListener_updatesRateToMinForContinuousProperty() {
1562         mService.registerListener(CONTINUOUS_READ_ONLY_PROPERTY_ID, MIN_SAMPLE_RATE - 1,
1563                 mICarPropertyEventListener);
1564         verify(mHalService).subscribeProperty(List.of(createCarSubscriptionOption(
1565                 CONTINUOUS_READ_ONLY_PROPERTY_ID, new int[]{0}, MIN_SAMPLE_RATE,
1566                 /* enableVur= */ true, /* resolution */ 0.0f)));
1567     }
1568 
1569     @Test
registerListener_updatesRateToMaxForContinuousProperty()1570     public void registerListener_updatesRateToMaxForContinuousProperty() {
1571         mService.registerListener(CONTINUOUS_READ_ONLY_PROPERTY_ID, MAX_SAMPLE_RATE + 1,
1572                 mICarPropertyEventListener);
1573         verify(mHalService).subscribeProperty(List.of(createCarSubscriptionOption(
1574                 CONTINUOUS_READ_ONLY_PROPERTY_ID, new int[]{0}, MAX_SAMPLE_RATE,
1575                 /* enableVur= */ true, /* resolution */ 0.0f)));
1576     }
1577 
1578     @Test
registerListenerSafe_VurExplicitOn()1579     public void registerListenerSafe_VurExplicitOn() {
1580         mService.registerListenerSafe(CONTINUOUS_READ_ONLY_PROPERTY_ID, MIN_SAMPLE_RATE,
1581                 /* enableVariableUpdateRate= */ true,
1582                 mICarPropertyEventListener);
1583 
1584         verify(mHalService).subscribeProperty(List.of(createCarSubscriptionOption(
1585                 CONTINUOUS_READ_ONLY_PROPERTY_ID, new int[]{0}, MIN_SAMPLE_RATE,
1586                 /* enableVur= */ true, /* resolution */ 0.0f)));
1587     }
1588 
1589     @Test
registerListenerSafe_returnsTrueWhenSuccessful()1590     public void registerListenerSafe_returnsTrueWhenSuccessful() {
1591         assertThat(mService.registerListenerSafe(ON_CHANGE_READ_WRITE_PROPERTY_ID,
1592                 CarPropertyManager.SENSOR_RATE_NORMAL, mICarPropertyEventListener)).isTrue();
1593     }
1594 
1595     @Test
registerListenerSafe_noExceptionBecauseOfUnsupportedPropertyIdAndReturnsFalse()1596     public void registerListenerSafe_noExceptionBecauseOfUnsupportedPropertyIdAndReturnsFalse() {
1597         assertThat(mService.registerListenerSafe(VehiclePropertyIds.INVALID,
1598                 CarPropertyManager.SENSOR_RATE_NORMAL, mICarPropertyEventListener)).isFalse();
1599     }
1600 
1601     @Test
unregisterListener_throwsExceptionBecauseOfNullListener()1602     public void unregisterListener_throwsExceptionBecauseOfNullListener() {
1603         assertThrows(NullPointerException.class,
1604                 () -> mService.unregisterListener(ON_CHANGE_READ_WRITE_PROPERTY_ID,
1605                         /* iCarPropertyEventListener= */ null));
1606     }
1607 
1608     @Test
unregisterListener_throwsExceptionBecauseOfUnsupportedPropertyId()1609     public void unregisterListener_throwsExceptionBecauseOfUnsupportedPropertyId() {
1610         assertThrows(IllegalArgumentException.class,
1611                 () -> mService.unregisterListener(VehiclePropertyIds.INVALID,
1612                         mICarPropertyEventListener));
1613     }
1614 
1615     @Test
unregisterListenerSafe_returnsTrueWhenSuccessful()1616     public void unregisterListenerSafe_returnsTrueWhenSuccessful() {
1617         assertThat(mService.unregisterListenerSafe(ON_CHANGE_READ_WRITE_PROPERTY_ID,
1618                 mICarPropertyEventListener)).isTrue();
1619     }
1620 
1621     @Test
unregisterListenerSafe_noExceptionBecauseOfUnsupportedPropertyIdAndReturnsFalse()1622     public void unregisterListenerSafe_noExceptionBecauseOfUnsupportedPropertyIdAndReturnsFalse() {
1623         assertThat(mService.unregisterListenerSafe(VehiclePropertyIds.INVALID,
1624                 mICarPropertyEventListener)).isFalse();
1625     }
1626 
1627     @Test
testCancelRequests()1628     public void testCancelRequests() {
1629         int[] requestIds = new int[]{1, 2};
1630 
1631         mService.cancelRequests(requestIds);
1632 
1633         verify(mHalService).cancelRequests(requestIds);
1634     }
1635 
1636     // Test that limited number of sync operations are allowed at once.
1637     @Test
testSyncOperationLimit()1638     public void testSyncOperationLimit() throws Exception {
1639         CountDownLatch startCd = new CountDownLatch(16);
1640         CountDownLatch finishCd = new CountDownLatch(16);
1641         CountDownLatch returnCd = new CountDownLatch(1);
1642         when(mHalService.getProperty(CONTINUOUS_READ_ONLY_PROPERTY_ID, 0))
1643                 .thenReturn(new CarPropertyValue(HVAC_TEMP, /* areaId= */ 0, Float.valueOf(11f)));
1644         doAnswer((invocation) -> {
1645             // Notify the operation has started.
1646             startCd.countDown();
1647 
1648             Log.d(TAG, "simulate sync operation ongoing, waiting for signal before finish.");
1649 
1650             // Wait for the signal before finishing the operation.
1651             returnCd.await(10, TimeUnit.SECONDS);
1652             return null;
1653         }).when(mHalService).setProperty(any());
1654 
1655         Executor executor = Executors.newFixedThreadPool(16);
1656         for (int i = 0; i < 16; i++) {
1657             executor.execute(() -> {
1658                 mService.setProperty(
1659                         new CarPropertyValue(WRITE_ONLY_INT_PROPERTY_ID, GLOBAL_AREA_ID,
1660                                 Integer.valueOf(11)),
1661                         mICarPropertyEventListener);
1662                 finishCd.countDown();
1663             });
1664         }
1665 
1666         // Wait until 16 requests are ongoing.
1667         startCd.await(10, TimeUnit.SECONDS);
1668 
1669         // The 17th request must throw exception.
1670         Exception e = assertThrows(ServiceSpecificException.class, () -> mService.getProperty(
1671                 CONTINUOUS_READ_ONLY_PROPERTY_ID, /* areaId= */ 0));
1672         assertThat(((ServiceSpecificException) e).errorCode).isEqualTo(SYNC_OP_LIMIT_TRY_AGAIN);
1673 
1674         // Unblock the operations.
1675         returnCd.countDown();
1676         assertWithMessage("All setProperty operations finished within 10 seconds")
1677                 .that(finishCd.await(10, TimeUnit.SECONDS)).isTrue();
1678     }
1679 
1680     @Test
getSupportedNoReadPermPropIds()1681     public void getSupportedNoReadPermPropIds() throws Exception {
1682         int invalidPropertyID = -1;
1683         assertThat(mService.getSupportedNoReadPermPropIds(new int[] {
1684                 CONTINUOUS_READ_ONLY_PROPERTY_ID, WRITE_ONLY_INT_PROPERTY_ID, invalidPropertyID
1685         })).isEqualTo(new int[]{WRITE_ONLY_INT_PROPERTY_ID});
1686     }
1687 
1688     @Test
isSupportedAndHasWritePermissionOnly_unsupported()1689     public void isSupportedAndHasWritePermissionOnly_unsupported() throws Exception {
1690         assertThat(mService.isSupportedAndHasWritePermissionOnly(-1)).isFalse();
1691     }
1692 
1693     @Test
isSupportedAndHasWritePermissionOnly_writeOnly()1694     public void isSupportedAndHasWritePermissionOnly_writeOnly() throws Exception {
1695         assertThat(mService.isSupportedAndHasWritePermissionOnly(WRITE_ONLY_INT_PROPERTY_ID))
1696                 .isTrue();
1697     }
1698 
1699     @Test
isSupportedAndHasWritePermissionOnly_readOnly()1700     public void isSupportedAndHasWritePermissionOnly_readOnly() throws Exception {
1701         assertThat(mService.isSupportedAndHasWritePermissionOnly(CONTINUOUS_READ_ONLY_PROPERTY_ID))
1702                 .isFalse();
1703     }
1704 
1705     /** Creates a {@code CarSubscription} with Vur off. */
createCarSubscriptionOption(int propertyId, int[] areaId, float updateRateHz)1706     private static CarSubscription createCarSubscriptionOption(int propertyId,
1707             int[] areaId, float updateRateHz) {
1708         return createCarSubscriptionOption(propertyId, areaId, updateRateHz,
1709                 /* enableVur= */ false, /*resolution*/ 0.0f);
1710     }
1711 
1712     /** Creates a {@code CarSubscription}. */
createCarSubscriptionOption(int propertyId, int[] areaId, float updateRateHz, boolean enableVur)1713     private static CarSubscription createCarSubscriptionOption(int propertyId,
1714             int[] areaId, float updateRateHz, boolean enableVur) {
1715         return createCarSubscriptionOption(propertyId, areaId, updateRateHz,
1716                 enableVur, /*resolution*/ 0.0f);
1717     }
1718 
1719     /** Creates a {@code CarSubscription}. */
createCarSubscriptionOption(int propertyId, int[] areaId, float updateRateHz, boolean enableVur, float resolution)1720     private static CarSubscription createCarSubscriptionOption(int propertyId,
1721             int[] areaId, float updateRateHz, boolean enableVur, float resolution) {
1722         CarSubscription options = new CarSubscription();
1723         options.propertyId = propertyId;
1724         options.areaIds = areaId;
1725         options.updateRateHz = updateRateHz;
1726         options.enableVariableUpdateRate = enableVur;
1727         options.resolution = resolution;
1728         return options;
1729     }
1730 }
1731