1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.car.hal;
18 
19 import static android.car.VehiclePropertyIds.CLUSTER_DISPLAY_STATE;
20 import static android.car.VehiclePropertyIds.CLUSTER_HEARTBEAT;
21 import static android.car.VehiclePropertyIds.CLUSTER_NAVIGATION_STATE;
22 import static android.car.VehiclePropertyIds.CLUSTER_REPORT_STATE;
23 import static android.car.VehiclePropertyIds.CLUSTER_REQUEST_DISPLAY;
24 import static android.car.VehiclePropertyIds.CLUSTER_SWITCH_UI;
25 
26 import static com.android.car.hal.ClusterHalService.DONT_CARE;
27 import static com.android.car.hal.VehicleHalTestingHelper.newSubscribableConfig;
28 
29 import static com.google.common.truth.Truth.assertThat;
30 
31 import static org.junit.Assert.assertThrows;
32 import static org.mockito.Mockito.times;
33 import static org.mockito.Mockito.verify;
34 import static org.mockito.Mockito.when;
35 
36 import android.content.Context;
37 import android.content.res.Resources;
38 import android.graphics.Insets;
39 import android.graphics.Rect;
40 
41 import com.android.car.R;
42 import com.android.car.hal.ClusterHalService.ClusterHalEventCallback;
43 
44 import org.junit.After;
45 import org.junit.Before;
46 import org.junit.Test;
47 import org.junit.runner.RunWith;
48 import org.mockito.ArgumentCaptor;
49 import org.mockito.Captor;
50 import org.mockito.Mock;
51 import org.mockito.junit.MockitoJUnitRunner;
52 
53 import java.util.ArrayList;
54 import java.util.Arrays;
55 import java.util.List;
56 
57 @RunWith(MockitoJUnitRunner.class)
58 public class ClusterHalServiceTest {
59     private static final int NOT_ASSIGNED = -1;
60 
61     private static final int ON = 1;
62     private static final int UI_TYPE_1 = 1;
63     private static final int UI_TYPE_2 = 2;
64     private static final byte[] UI_AVAILABILITY = new byte[] {(byte) 1, (byte) 1, (byte) 0};
65 
66     private static final int BOUNDS_LEFT = 0;
67     private static final int BOUNDS_TOP = 1;
68     private static final int BOUNDS_RIGHT = 800;
69     private static final int BOUNDS_BOTTOM = 601;
70     private static final int INSET_LEFT = 20;
71     private static final int INSET_TOP = 10;
72     private static final int INSET_RIGHT = 780;
73     private static final int INSET_BOTTOM = 590;
74 
75     private static final HalPropValueBuilder PROP_VALUE_BUILDER = new HalPropValueBuilder(
76             /*isAidl=*/true);
77 
78     @Mock
79     Context mMockContext;
80     @Mock
81     Resources mMockResources;
82     @Mock
83     VehicleHal mVehicleHal;
84     @Captor
85     ArgumentCaptor<HalPropValue> mPropCaptor;
86 
87     private ClusterHalService mClusterHalService;
88 
89     int mUiType = NOT_ASSIGNED;
90     int mOnOff = NOT_ASSIGNED;
91     Rect mBounds = null;
92     Insets mInsets = null;
93 
94     private final ClusterHalEventCallback mHalEventListener = new ClusterHalEventCallback() {
95         public void onSwitchUi(int uiType) {
96             mUiType = uiType;
97         }
98 
99         public void onDisplayState(int onOff, Rect bounds, Insets insets) {
100             mOnOff = onOff;
101             mBounds = bounds;
102             mInsets = insets;
103         }
104     };
105 
getIntValues(HalPropValue value)106     private ArrayList<Integer> getIntValues(HalPropValue value) {
107         ArrayList<Integer> intValues = new ArrayList<Integer>();
108         for (int i = 0; i < value.getInt32ValuesSize(); i++) {
109             intValues.add(value.getInt32Value(i));
110         }
111         return intValues;
112     }
113 
getFullProperties()114     private static List<HalPropConfig> getFullProperties() {
115         return Arrays.asList(
116                 newSubscribableConfig(CLUSTER_SWITCH_UI),
117                 newSubscribableConfig(CLUSTER_DISPLAY_STATE),
118                 newSubscribableConfig(CLUSTER_REPORT_STATE),
119                 newSubscribableConfig(CLUSTER_REQUEST_DISPLAY),
120                 newSubscribableConfig(CLUSTER_HEARTBEAT),
121                 newSubscribableConfig(CLUSTER_NAVIGATION_STATE));
122     }
123 
getCoreProperties()124     private static List<HalPropConfig> getCoreProperties() {
125         return Arrays.asList(
126                 newSubscribableConfig(CLUSTER_SWITCH_UI),
127                 newSubscribableConfig(CLUSTER_DISPLAY_STATE),
128                 newSubscribableConfig(CLUSTER_REPORT_STATE),
129                 newSubscribableConfig(CLUSTER_REQUEST_DISPLAY));
130     }
131 
createLightModeServiceWithProperties(List<HalPropConfig> properties)132     private ClusterHalService createLightModeServiceWithProperties(List<HalPropConfig> properties) {
133         when(mMockResources.getInteger(R.integer.config_clusterHomeServiceMode)).thenReturn(1);
134         ClusterHalService lightModeService = new ClusterHalService(mMockContext, mVehicleHal);
135         lightModeService.takeProperties(properties);
136         lightModeService.setCallback(mHalEventListener);
137 
138         return lightModeService;
139     }
140 
141     @Before
setUp()142     public void setUp() {
143         when(mMockContext.getResources()).thenReturn(mMockResources);
144         when(mMockResources.getInteger(R.integer.config_clusterHomeServiceMode)).thenReturn(0);
145         when(mVehicleHal.getHalPropValueBuilder()).thenReturn(PROP_VALUE_BUILDER);
146         mClusterHalService = new ClusterHalService(mMockContext, mVehicleHal);
147         mClusterHalService.setCallback(mHalEventListener);
148     }
149 
150     @After
tearDown()151     public void tearDown() {
152         mClusterHalService.release();
153         mClusterHalService = null;
154     }
155 
156     @Test
testIsServiceEnabled_fullMode_coreProperties()157     public void testIsServiceEnabled_fullMode_coreProperties() {
158         mClusterHalService.takeProperties(getCoreProperties());
159 
160         assertThat(mClusterHalService.isServiceEnabled()).isTrue();
161     }
162 
163     @Test
testIsServiceEnabled_fullMode_partialProperties()164     public void testIsServiceEnabled_fullMode_partialProperties() {
165         mClusterHalService.takeProperties(Arrays.asList(
166                 newSubscribableConfig(CLUSTER_DISPLAY_STATE)));
167 
168         assertThat(mClusterHalService.isServiceEnabled()).isFalse();
169     }
170 
171     @Test
testIsServiceEnabled_fullMode_noProperties()172     public void testIsServiceEnabled_fullMode_noProperties() {
173         mClusterHalService.takeProperties(Arrays.asList());
174 
175         assertThat(mClusterHalService.isServiceEnabled()).isFalse();
176     }
177 
178     @Test
testIsServiceEnabled_lightMode_partialProperties()179     public void testIsServiceEnabled_lightMode_partialProperties() {
180         ClusterHalService lightModeService = createLightModeServiceWithProperties(Arrays.asList(
181                 newSubscribableConfig(CLUSTER_DISPLAY_STATE)));
182 
183         assertThat(lightModeService.isServiceEnabled()).isTrue();
184     }
185 
186     @Test
testIsServiceEnabled_lightMode_noProperties()187     public void testIsServiceEnabled_lightMode_noProperties() {
188         ClusterHalService lightModeService = createLightModeServiceWithProperties(Arrays.asList());
189 
190         assertThat(lightModeService.isServiceEnabled()).isTrue();
191     }
192 
193     @Test
testInit_subscribePropertySafe_coreProperties()194     public void testInit_subscribePropertySafe_coreProperties() {
195         mClusterHalService.takeProperties(getCoreProperties());
196 
197         mClusterHalService.init();
198 
199         verify(mVehicleHal).subscribePropertySafe(mClusterHalService, CLUSTER_SWITCH_UI);
200         verify(mVehicleHal).subscribePropertySafe(mClusterHalService, CLUSTER_DISPLAY_STATE);
201     }
202 
203     @Test
testInit_subscribePropertySafe_lightMode_coreProperties()204     public void testInit_subscribePropertySafe_lightMode_coreProperties() {
205         ClusterHalService lightModeService =
206                 createLightModeServiceWithProperties(getCoreProperties());
207 
208         lightModeService.init();
209 
210         verify(mVehicleHal, times(0)).subscribePropertySafe(lightModeService, CLUSTER_SWITCH_UI);
211         verify(mVehicleHal, times(0)).subscribePropertySafe(lightModeService,
212                 CLUSTER_DISPLAY_STATE);
213     }
214 
215     @Test
testInit_subscribePropertySafe_noProperties()216     public void testInit_subscribePropertySafe_noProperties() {
217         mClusterHalService.takeProperties(Arrays.asList());
218 
219         mClusterHalService.init();
220 
221         verify(mVehicleHal, times(0)).subscribePropertySafe(mClusterHalService, CLUSTER_SWITCH_UI);
222         verify(mVehicleHal, times(0)).subscribePropertySafe(mClusterHalService,
223                 CLUSTER_DISPLAY_STATE);
224     }
225 
226     @Test
testInit_lightMode_subscribePropertySafe_noProperties()227     public void testInit_lightMode_subscribePropertySafe_noProperties() {
228         ClusterHalService lightModeService = createLightModeServiceWithProperties(Arrays.asList());
229 
230         lightModeService.init();
231 
232         verify(mVehicleHal, times(0)).subscribePropertySafe(lightModeService, CLUSTER_SWITCH_UI);
233         verify(mVehicleHal, times(0)).subscribePropertySafe(lightModeService,
234                 CLUSTER_DISPLAY_STATE);
235     }
236 
237     @Test
testInit_subscribePropertySafe_partialProperties()238     public void testInit_subscribePropertySafe_partialProperties() {
239         mClusterHalService.takeProperties(Arrays.asList(
240                 newSubscribableConfig(CLUSTER_DISPLAY_STATE)));
241 
242         mClusterHalService.init();
243 
244         verify(mVehicleHal, times(0)).subscribePropertySafe(mClusterHalService, CLUSTER_SWITCH_UI);
245         verify(mVehicleHal, times(0)).subscribePropertySafe(mClusterHalService,
246                 CLUSTER_DISPLAY_STATE);
247     }
248 
249     @Test
testInit_lightMode_subscribePropertySafe_partialProperties()250     public void testInit_lightMode_subscribePropertySafe_partialProperties() {
251         ClusterHalService lightModeService = createLightModeServiceWithProperties(Arrays.asList(
252                 newSubscribableConfig(CLUSTER_DISPLAY_STATE)));
253 
254         lightModeService.init();
255 
256         verify(mVehicleHal, times(0)).subscribePropertySafe(lightModeService, CLUSTER_SWITCH_UI);
257         verify(mVehicleHal, times(0)).subscribePropertySafe(lightModeService,
258                 CLUSTER_DISPLAY_STATE);
259     }
260 
261     @Test
testTakeProperties_fullMode_noProperties()262     public void testTakeProperties_fullMode_noProperties() {
263         mClusterHalService.takeProperties(Arrays.asList());
264 
265         assertThat(mClusterHalService.isFullModeEnabled()).isFalse();
266         assertThat(mClusterHalService.isServiceEnabled()).isFalse();
267         assertThat(mClusterHalService.isNavigationStateSupported()).isFalse();
268     }
269 
270     @Test
testTakeProperties_lightMode_noProperties()271     public void testTakeProperties_lightMode_noProperties() {
272         ClusterHalService lightModeService = createLightModeServiceWithProperties(Arrays.asList());
273 
274         assertThat(lightModeService.isFullModeEnabled()).isFalse();
275         assertThat(lightModeService.isServiceEnabled()).isTrue();
276         assertThat(lightModeService.isNavigationStateSupported()).isFalse();
277     }
278 
279     @Test
testTakeProperties_fullMode_partialProperties()280     public void testTakeProperties_fullMode_partialProperties() {
281         mClusterHalService.takeProperties(Arrays.asList(
282                 newSubscribableConfig(CLUSTER_SWITCH_UI),
283                 newSubscribableConfig(CLUSTER_DISPLAY_STATE),
284                 newSubscribableConfig(CLUSTER_REPORT_STATE)));
285 
286         assertThat(mClusterHalService.isFullModeEnabled()).isFalse();
287         assertThat(mClusterHalService.isServiceEnabled()).isFalse();
288         assertThat(mClusterHalService.isNavigationStateSupported()).isFalse();
289     }
290 
291     @Test
testTakeProperties_lightMode_partialProperties()292     public void testTakeProperties_lightMode_partialProperties() {
293         ClusterHalService lightModeService = createLightModeServiceWithProperties(Arrays.asList(
294                 newSubscribableConfig(CLUSTER_SWITCH_UI),
295                 newSubscribableConfig(CLUSTER_DISPLAY_STATE),
296                 newSubscribableConfig(CLUSTER_REPORT_STATE)));
297 
298         assertThat(lightModeService.isFullModeEnabled()).isFalse();
299         assertThat(lightModeService.isServiceEnabled()).isTrue();
300         assertThat(lightModeService.isNavigationStateSupported()).isFalse();
301     }
302 
303     @Test
testTakeProperties_fullMode_coreProperties()304     public void testTakeProperties_fullMode_coreProperties() {
305         mClusterHalService.takeProperties(getCoreProperties());
306 
307         assertThat(mClusterHalService.isFullModeEnabled()).isTrue();
308         assertThat(mClusterHalService.isServiceEnabled()).isTrue();
309         assertThat(mClusterHalService.isNavigationStateSupported()).isFalse();
310     }
311 
312     @Test
testTakeProperties_fullMode_fullProperties()313     public void testTakeProperties_fullMode_fullProperties() {
314         mClusterHalService.takeProperties(getFullProperties());
315 
316         assertThat(mClusterHalService.isFullModeEnabled()).isTrue();
317         assertThat(mClusterHalService.isServiceEnabled()).isTrue();
318         assertThat(mClusterHalService.isNavigationStateSupported()).isTrue();
319     }
320 
321     @Test
testTakeProperties_lightMode_fullProperties()322     public void testTakeProperties_lightMode_fullProperties() {
323         ClusterHalService lightModeService =
324                 createLightModeServiceWithProperties(getFullProperties());
325 
326         assertThat(lightModeService.isFullModeEnabled()).isFalse();
327         assertThat(lightModeService.isServiceEnabled()).isTrue();
328         assertThat(lightModeService.isNavigationStateSupported()).isTrue();
329     }
330 
createSwitchUiEvent(int uiType)331     private static HalPropValue createSwitchUiEvent(int uiType) {
332         HalPropValue event = PROP_VALUE_BUILDER.build(CLUSTER_SWITCH_UI, /*areaId=*/0, uiType);
333         return event;
334     }
335 
createDisplayStateEvent(int onOff, int boundsLeft, int boundsTop, int boundsRight, int boundsBottom, int insetsLeft, int insetsTop, int insetSRight, int insetSBottom)336     private static HalPropValue createDisplayStateEvent(int onOff,
337             int boundsLeft, int boundsTop, int boundsRight, int boundsBottom,
338             int insetsLeft, int insetsTop, int insetSRight, int insetSBottom) {
339         HalPropValue event = PROP_VALUE_BUILDER.build(CLUSTER_DISPLAY_STATE, /*areaId=*/0,
340                 new int[]{onOff, boundsLeft, boundsTop, boundsRight, boundsBottom, insetsLeft,
341                         insetsTop, insetSRight, insetSBottom});
342         return event;
343     }
344 
345     @Test
testOnSwitchUi()346     public void testOnSwitchUi() {
347         mClusterHalService.takeProperties(getCoreProperties());
348 
349         mClusterHalService.onHalEvents(Arrays.asList(createSwitchUiEvent(UI_TYPE_1)));
350 
351         assertThat(mUiType).isEqualTo(UI_TYPE_1);
352     }
353 
354     @Test
testOnSwitchUi_noListener()355     public void testOnSwitchUi_noListener() {
356         mClusterHalService.takeProperties(getCoreProperties());
357         mClusterHalService.setCallback(null);
358 
359         mClusterHalService.onHalEvents(Arrays.asList(createSwitchUiEvent(UI_TYPE_1)));
360 
361         assertThat(mUiType).isEqualTo(NOT_ASSIGNED);
362     }
363 
364     @Test
testOnSwitchUi_noProperties()365     public void testOnSwitchUi_noProperties() {
366         mClusterHalService.takeProperties(Arrays.asList());
367 
368         mClusterHalService.onHalEvents(Arrays.asList(createSwitchUiEvent(UI_TYPE_1)));
369 
370         assertThat(mUiType).isEqualTo(NOT_ASSIGNED);
371     }
372 
373     @Test
testOnDisplayState()374     public void testOnDisplayState() {
375         mClusterHalService.takeProperties(getCoreProperties());
376 
377         mClusterHalService.onHalEvents(Arrays.asList(
378                 createDisplayStateEvent(ON, BOUNDS_LEFT, BOUNDS_TOP, BOUNDS_RIGHT, BOUNDS_BOTTOM,
379                         INSET_LEFT, INSET_TOP, INSET_RIGHT, INSET_BOTTOM)));
380 
381         assertThat(mOnOff).isEqualTo(ON);
382         assertThat(mBounds.left).isEqualTo(BOUNDS_LEFT);
383         assertThat(mBounds.top).isEqualTo(BOUNDS_TOP);
384         assertThat(mBounds.right).isEqualTo(BOUNDS_RIGHT);
385         assertThat(mBounds.bottom).isEqualTo(BOUNDS_BOTTOM);
386         assertThat(mInsets.left).isEqualTo(INSET_LEFT);
387         assertThat(mInsets.top).isEqualTo(INSET_TOP);
388         assertThat(mInsets.right).isEqualTo(INSET_RIGHT);
389         assertThat(mInsets.bottom).isEqualTo(INSET_BOTTOM);
390     }
391 
392     @Test
testOnDisplayState_DontAcceptPartialDontCare_Bounds()393     public void testOnDisplayState_DontAcceptPartialDontCare_Bounds() {
394         mClusterHalService.takeProperties(getCoreProperties());
395         mClusterHalService.onHalEvents(Arrays.asList(
396                 createDisplayStateEvent(ON, BOUNDS_LEFT, BOUNDS_TOP, BOUNDS_RIGHT, DONT_CARE,
397                         INSET_LEFT, INSET_TOP, INSET_RIGHT, INSET_BOTTOM)));
398 
399         assertThat(mOnOff).isEqualTo(ON);
400         assertThat(mBounds).isNull();
401         assertThat(mInsets.left).isEqualTo(INSET_LEFT);
402         assertThat(mInsets.top).isEqualTo(INSET_TOP);
403         assertThat(mInsets.right).isEqualTo(INSET_RIGHT);
404         assertThat(mInsets.bottom).isEqualTo(INSET_BOTTOM);
405     }
406 
407     @Test
testOnDisplayState_DontAcceptPartialDontCare_Inset()408     public void testOnDisplayState_DontAcceptPartialDontCare_Inset() {
409         mClusterHalService.takeProperties(getCoreProperties());
410         mClusterHalService.onHalEvents(Arrays.asList(
411                 createDisplayStateEvent(ON, BOUNDS_LEFT, BOUNDS_TOP, BOUNDS_RIGHT, BOUNDS_BOTTOM,
412                         INSET_LEFT, INSET_TOP, INSET_RIGHT, DONT_CARE)));
413 
414         assertThat(mOnOff).isEqualTo(ON);
415         assertThat(mBounds.left).isEqualTo(BOUNDS_LEFT);
416         assertThat(mBounds.top).isEqualTo(BOUNDS_TOP);
417         assertThat(mBounds.right).isEqualTo(BOUNDS_RIGHT);
418         assertThat(mBounds.bottom).isEqualTo(BOUNDS_BOTTOM);
419         assertThat(mInsets).isNull();
420     }
421 
422     @Test
testOnDisplayState_noListener()423     public void testOnDisplayState_noListener() {
424         mClusterHalService.takeProperties(getCoreProperties());
425         mClusterHalService.setCallback(null);
426 
427         mClusterHalService.onHalEvents(Arrays.asList(
428                 createDisplayStateEvent(ON, BOUNDS_LEFT, BOUNDS_TOP, BOUNDS_RIGHT, BOUNDS_BOTTOM,
429                         INSET_LEFT, INSET_TOP, INSET_RIGHT, INSET_BOTTOM)));
430 
431         assertThat(mOnOff).isEqualTo(NOT_ASSIGNED);
432         assertThat(mBounds).isNull();
433         assertThat(mInsets).isNull();
434     }
435 
436     @Test
testOnDisplayState_noProperties()437     public void testOnDisplayState_noProperties() {
438         mClusterHalService.takeProperties(Arrays.asList());
439 
440         mClusterHalService.onHalEvents(Arrays.asList(
441                 createDisplayStateEvent(ON, BOUNDS_LEFT, BOUNDS_TOP, BOUNDS_RIGHT, BOUNDS_BOTTOM,
442                         INSET_LEFT, INSET_TOP, INSET_RIGHT, INSET_BOTTOM)));
443 
444         assertThat(mOnOff).isEqualTo(NOT_ASSIGNED);
445         assertThat(mBounds).isNull();
446         assertThat(mInsets).isNull();
447     }
448 
449     @Test
testReportState()450     public void testReportState() {
451         mClusterHalService.takeProperties(getCoreProperties());
452         mClusterHalService.reportState(
453                 ON, new Rect(BOUNDS_LEFT, BOUNDS_TOP, BOUNDS_RIGHT, BOUNDS_BOTTOM),
454                 Insets.of(INSET_LEFT, INSET_TOP, INSET_RIGHT, INSET_BOTTOM),
455                 UI_TYPE_1, UI_TYPE_2, UI_AVAILABILITY);
456 
457         verify(mVehicleHal).set(mPropCaptor.capture());
458         HalPropValue prop = mPropCaptor.getValue();
459         assertThat(prop.getPropId()).isEqualTo(CLUSTER_REPORT_STATE);
460         assertThat(getIntValues(prop)).containsExactly(
461                 ON, BOUNDS_LEFT, BOUNDS_TOP, BOUNDS_RIGHT, BOUNDS_BOTTOM,
462                 INSET_LEFT, INSET_TOP, INSET_RIGHT, INSET_BOTTOM, UI_TYPE_1, UI_TYPE_2);
463         assertThat(prop.getByteArray()).asList().containsExactly(
464                 (Byte) UI_AVAILABILITY[0], (Byte) UI_AVAILABILITY[1], (Byte) UI_AVAILABILITY[2]);
465     }
466 
467     @Test
testReportState_noProperties()468     public void testReportState_noProperties() {
469         mClusterHalService.takeProperties(Arrays.asList());
470 
471         assertThrows(IllegalStateException.class,
472                 () -> mClusterHalService.reportState(
473                         ON, new Rect(BOUNDS_LEFT, BOUNDS_TOP, BOUNDS_RIGHT, BOUNDS_BOTTOM),
474                         Insets.of(INSET_LEFT, INSET_TOP, INSET_RIGHT, INSET_BOTTOM),
475                         UI_TYPE_1, UI_TYPE_2, UI_AVAILABILITY));
476     }
477 
478     @Test
testReportState_lightMode_fullProperties()479     public void testReportState_lightMode_fullProperties() {
480         ClusterHalService lightModeService =
481                 createLightModeServiceWithProperties(getFullProperties());
482 
483         assertThrows(IllegalStateException.class,
484                 () -> lightModeService.reportState(
485                         ON, new Rect(BOUNDS_LEFT, BOUNDS_TOP, BOUNDS_RIGHT, BOUNDS_BOTTOM),
486                         Insets.of(INSET_LEFT, INSET_TOP, INSET_RIGHT, INSET_BOTTOM),
487                         UI_TYPE_1, UI_TYPE_2, UI_AVAILABILITY));
488     }
489 
490     @Test
testRequestDisplay()491     public void testRequestDisplay() {
492         mClusterHalService.takeProperties(getCoreProperties());
493         mClusterHalService.requestDisplay(UI_TYPE_2);
494 
495         verify(mVehicleHal).set(mPropCaptor.capture());
496         HalPropValue prop = mPropCaptor.getValue();
497         assertThat(prop.getPropId()).isEqualTo(CLUSTER_REQUEST_DISPLAY);
498         assertThat(getIntValues(prop)).containsExactly(UI_TYPE_2);
499     }
500 
501     @Test
testRequestDisplay_noProperties()502     public void testRequestDisplay_noProperties() {
503         mClusterHalService.takeProperties(Arrays.asList());
504 
505         assertThrows(IllegalStateException.class,
506                 () -> mClusterHalService.requestDisplay(UI_TYPE_2));
507     }
508 
509     @Test
testRequestDisplay_lightMode_fullProperties()510     public void testRequestDisplay_lightMode_fullProperties() {
511         ClusterHalService lightModeService =
512                 createLightModeServiceWithProperties(getFullProperties());
513 
514         assertThrows(IllegalStateException.class,
515                 () -> lightModeService.requestDisplay(UI_TYPE_2));
516     }
517 
518     @Test
testSendNavigationState()519     public void testSendNavigationState() {
520         mClusterHalService.takeProperties(getFullProperties());
521         mClusterHalService.sendNavigationState(new byte[]{1, 2, 3, 4});
522 
523         verify(mVehicleHal).set(mPropCaptor.capture());
524         HalPropValue prop = mPropCaptor.getValue();
525         assertThat(prop.getPropId()).isEqualTo(CLUSTER_NAVIGATION_STATE);
526         assertThat(prop.getByteArray()).asList()
527                 .containsExactly((byte) 1, (byte) 2, (byte) 3, (byte) 4);
528     }
529 
530     @Test
testSendNavigationState_noProperties()531     public void testSendNavigationState_noProperties() {
532         mClusterHalService.takeProperties(Arrays.asList());
533 
534         mClusterHalService.sendNavigationState(new byte[]{1, 2, 3, 4});
535 
536         verify(mVehicleHal, times(0)).set(mPropCaptor.capture());
537     }
538 
539     @Test
testSendHeartbeat_noProperties()540     public void testSendHeartbeat_noProperties() {
541         mClusterHalService.takeProperties(Arrays.asList());
542 
543         mClusterHalService.sendHeartbeat(/* epochTimeNs= */ 0, /* visibility= */ 0,
544                 /* appMetadata= */ null);
545 
546         verify(mVehicleHal, times(0)).set(mPropCaptor.capture());
547     }
548 
549     @Test
testSendHeartbeat()550     public void testSendHeartbeat() {
551         mClusterHalService.takeProperties(getFullProperties());
552 
553         long epochTimeNs = 123456789;
554         long visibility = 1;
555         byte[] appMetadata = new byte[]{3, 2, 0, 1};
556         mClusterHalService.sendHeartbeat(epochTimeNs, visibility, appMetadata);
557 
558         verify(mVehicleHal).set(mPropCaptor.capture());
559         HalPropValue prop = mPropCaptor.getValue();
560         assertThat(prop.getPropId()).isEqualTo(CLUSTER_HEARTBEAT);
561         assertThat(prop.getInt64ContainerArray()).asList()
562                 .containsExactly((long) epochTimeNs, (long) visibility);
563         assertThat(prop.getByteArray()).asList()
564                 .containsExactly((byte) 3, (byte) 2, (byte) 0, (byte) 1);
565     }
566 
567 }
568