• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.car.hal;
17 
18 import static android.car.CarOccupantZoneManager.DisplayTypeEnum;
19 import static android.hardware.automotive.vehicle.V2_0.CustomInputType.CUSTOM_EVENT_F1;
20 
21 import static com.google.common.truth.Truth.assertThat;
22 
23 import static org.mockito.ArgumentMatchers.any;
24 import static org.mockito.ArgumentMatchers.anyInt;
25 import static org.mockito.ArgumentMatchers.eq;
26 import static org.mockito.Mockito.doAnswer;
27 import static org.mockito.Mockito.never;
28 import static org.mockito.Mockito.reset;
29 import static org.mockito.Mockito.times;
30 import static org.mockito.Mockito.verify;
31 import static org.mockito.Mockito.when;
32 
33 import android.car.CarOccupantZoneManager;
34 import android.car.input.CarInputManager;
35 import android.car.input.CustomInputEvent;
36 import android.car.input.RotaryEvent;
37 import android.hardware.automotive.vehicle.V2_0.RotaryInputType;
38 import android.hardware.automotive.vehicle.V2_0.VehicleDisplay;
39 import android.hardware.automotive.vehicle.V2_0.VehicleHwKeyInputAction;
40 import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig;
41 import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
42 import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
43 import android.view.Display;
44 import android.view.KeyEvent;
45 
46 import androidx.test.filters.RequiresDevice;
47 
48 import com.android.car.vehiclehal.test.VehiclePropConfigBuilder;
49 
50 import com.google.common.collect.ImmutableList;
51 import com.google.common.collect.ImmutableSet;
52 
53 import org.junit.After;
54 import org.junit.Before;
55 import org.junit.Test;
56 import org.junit.runner.RunWith;
57 import org.mockito.ArgumentCaptor;
58 import org.mockito.Mock;
59 import org.mockito.junit.MockitoJUnitRunner;
60 
61 import java.util.ArrayList;
62 import java.util.List;
63 import java.util.Set;
64 import java.util.function.LongSupplier;
65 
66 @RunWith(MockitoJUnitRunner.class)
67 public class InputHalServiceTest {
68     @Mock VehicleHal mVehicleHal;
69     @Mock InputHalService.InputListener mInputListener;
70     @Mock LongSupplier mUptimeSupplier;
71 
72     private static final VehiclePropConfig HW_KEY_INPUT_CONFIG =
73             VehiclePropConfigBuilder.newBuilder(VehicleProperty.HW_KEY_INPUT).build();
74     private static final VehiclePropConfig HW_ROTARY_INPUT_CONFIG =
75             VehiclePropConfigBuilder.newBuilder(VehicleProperty.HW_ROTARY_INPUT).build();
76     private static final VehiclePropConfig HW_CUSTOM_INPUT_CONFIG =
77             VehiclePropConfigBuilder.newBuilder(VehicleProperty.HW_CUSTOM_INPUT).build();
78 
79     private enum Key {DOWN, UP}
80 
81     private InputHalService mInputHalService;
82 
83     @Before
setUp()84     public void setUp() {
85         when(mUptimeSupplier.getAsLong()).thenReturn(0L);
86         mInputHalService = new InputHalService(mVehicleHal, mUptimeSupplier);
87         mInputHalService.init();
88     }
89 
90     @After
tearDown()91     public void tearDown() {
92         mInputHalService.release();
93         mInputHalService = null;
94     }
95 
96     @Test
ignoresSetListener_beforeKeyInputSupported()97     public void ignoresSetListener_beforeKeyInputSupported() {
98         assertThat(mInputHalService.isKeyInputSupported()).isFalse();
99 
100         mInputHalService.setInputListener(mInputListener);
101 
102         int anyDisplay = VehicleDisplay.MAIN;
103         mInputHalService.onHalEvents(
104                 ImmutableList.of(makeKeyPropValue(Key.DOWN, KeyEvent.KEYCODE_ENTER, anyDisplay)));
105         verify(mInputListener, never()).onKeyEvent(any(), anyInt());
106     }
107 
108     @Test
takesKeyInputProperty()109     public void takesKeyInputProperty() {
110         Set<VehiclePropConfig> offeredProps = ImmutableSet.of(
111                 VehiclePropConfigBuilder.newBuilder(VehicleProperty.ABS_ACTIVE).build(),
112                 HW_KEY_INPUT_CONFIG,
113                 VehiclePropConfigBuilder.newBuilder(VehicleProperty.CURRENT_GEAR).build());
114 
115         mInputHalService.takeProperties(offeredProps);
116 
117         assertThat(mInputHalService.isKeyInputSupported()).isTrue();
118         assertThat(mInputHalService.isRotaryInputSupported()).isFalse();
119         assertThat(mInputHalService.isCustomInputSupported()).isFalse();
120     }
121 
122     @Test
takesRotaryInputProperty()123     public void takesRotaryInputProperty() {
124         Set<VehiclePropConfig> offeredProps = ImmutableSet.of(
125                 VehiclePropConfigBuilder.newBuilder(VehicleProperty.ABS_ACTIVE).build(),
126                 HW_ROTARY_INPUT_CONFIG,
127                 VehiclePropConfigBuilder.newBuilder(VehicleProperty.CURRENT_GEAR).build());
128 
129         mInputHalService.takeProperties(offeredProps);
130 
131         assertThat(mInputHalService.isRotaryInputSupported()).isTrue();
132         assertThat(mInputHalService.isKeyInputSupported()).isFalse();
133         assertThat(mInputHalService.isCustomInputSupported()).isFalse();
134     }
135 
136     @Test
takesCustomInputProperty()137     public void takesCustomInputProperty() {
138         Set<VehiclePropConfig> offeredProps = ImmutableSet.of(
139                 VehiclePropConfigBuilder.newBuilder(VehicleProperty.ABS_ACTIVE).build(),
140                 HW_CUSTOM_INPUT_CONFIG,
141                 VehiclePropConfigBuilder.newBuilder(VehicleProperty.CURRENT_GEAR).build());
142 
143         mInputHalService.takeProperties(offeredProps);
144 
145         assertThat(mInputHalService.isRotaryInputSupported()).isFalse();
146         assertThat(mInputHalService.isKeyInputSupported()).isFalse();
147         assertThat(mInputHalService.isCustomInputSupported()).isTrue();
148     }
149 
150     @Test
takesKeyAndRotaryAndCustomInputProperty()151     public void takesKeyAndRotaryAndCustomInputProperty() {
152         Set<VehiclePropConfig> offeredProps = ImmutableSet.of(
153                 VehiclePropConfigBuilder.newBuilder(VehicleProperty.ABS_ACTIVE).build(),
154                 HW_KEY_INPUT_CONFIG,
155                 HW_ROTARY_INPUT_CONFIG,
156                 HW_CUSTOM_INPUT_CONFIG,
157                 VehiclePropConfigBuilder.newBuilder(VehicleProperty.CURRENT_GEAR).build());
158 
159         mInputHalService.takeProperties(offeredProps);
160 
161         assertThat(mInputHalService.isKeyInputSupported()).isTrue();
162         assertThat(mInputHalService.isRotaryInputSupported()).isTrue();
163         assertThat(mInputHalService.isCustomInputSupported()).isTrue();
164     }
165 
166     @Test
dispatchesInputEvent_single_toListener_mainDisplay()167     public void dispatchesInputEvent_single_toListener_mainDisplay() {
168         subscribeListener();
169 
170         KeyEvent event = dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_ENTER, VehicleDisplay.MAIN,
171                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN);
172         assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_DOWN);
173         assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER);
174     }
175 
176     @Test
dispatchesInputEvent_single_toListener_clusterDisplay()177     public void dispatchesInputEvent_single_toListener_clusterDisplay() {
178         subscribeListener();
179 
180         KeyEvent event = dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_ENTER,
181                 VehicleDisplay.INSTRUMENT_CLUSTER,
182                 CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER);
183         assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_DOWN);
184         assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER);
185     }
186 
187     @Test
dispatchesInputEvent_multiple_toListener_mainDisplay()188     public void dispatchesInputEvent_multiple_toListener_mainDisplay() {
189         subscribeListener();
190 
191         // KeyEvents get recycled, so we can't just use ArgumentCaptor#getAllValues here.
192         // We need to make a copy of the information we need at the time of the call.
193         List<KeyEvent> events = new ArrayList<>();
194         doAnswer(inv -> {
195             KeyEvent event = inv.getArgument(0);
196             events.add(event.copy());
197             return null;
198         }).when(mInputListener).onKeyEvent(any(), eq(CarOccupantZoneManager.DISPLAY_TYPE_MAIN));
199 
200         mInputHalService.onHalEvents(
201                 ImmutableList.of(
202                         makeKeyPropValue(Key.DOWN, KeyEvent.KEYCODE_ENTER, VehicleDisplay.MAIN),
203                         makeKeyPropValue(Key.DOWN, KeyEvent.KEYCODE_MENU, VehicleDisplay.MAIN)));
204 
205         assertThat(events.get(0).getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER);
206         assertThat(events.get(1).getKeyCode()).isEqualTo(KeyEvent.KEYCODE_MENU);
207 
208         events.forEach(KeyEvent::recycle);
209     }
210 
211     @Test
dispatchesInputEvent_multiple_toListener_clusterDisplay()212     public void dispatchesInputEvent_multiple_toListener_clusterDisplay() {
213         subscribeListener();
214 
215         // KeyEvents get recycled, so we can't just use ArgumentCaptor#getAllValues here.
216         // We need to make a copy of the information we need at the time of the call.
217         List<KeyEvent> events = new ArrayList<>();
218         doAnswer(inv -> {
219             KeyEvent event = inv.getArgument(0);
220             events.add(event.copy());
221             return null;
222         }).when(mInputListener).onKeyEvent(any(),
223                 eq(CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER));
224 
225         mInputHalService.onHalEvents(
226                 ImmutableList.of(
227                         makeKeyPropValue(Key.DOWN, KeyEvent.KEYCODE_ENTER,
228                                 VehicleDisplay.INSTRUMENT_CLUSTER),
229                         makeKeyPropValue(Key.DOWN, KeyEvent.KEYCODE_MENU,
230                                 VehicleDisplay.INSTRUMENT_CLUSTER)));
231 
232         assertThat(events.get(0).getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER);
233         assertThat(events.get(1).getKeyCode()).isEqualTo(KeyEvent.KEYCODE_MENU);
234 
235         events.forEach(KeyEvent::recycle);
236     }
237 
238     @Test
handlesRepeatedKeys_anyDisplay()239     public void handlesRepeatedKeys_anyDisplay() {
240         subscribeListener();
241 
242         KeyEvent event = dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_ENTER, VehicleDisplay.MAIN,
243                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN);
244         assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_DOWN);
245         assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER);
246         assertThat(event.getEventTime()).isEqualTo(0L);
247         assertThat(event.getDownTime()).isEqualTo(0L);
248         assertThat(event.getRepeatCount()).isEqualTo(0);
249 
250         when(mUptimeSupplier.getAsLong()).thenReturn(5L);
251         event = dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_ENTER, VehicleDisplay.MAIN,
252                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN);
253 
254         assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_DOWN);
255         assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER);
256         assertThat(event.getEventTime()).isEqualTo(5L);
257         assertThat(event.getDownTime()).isEqualTo(5L);
258         assertThat(event.getRepeatCount()).isEqualTo(1);
259 
260         when(mUptimeSupplier.getAsLong()).thenReturn(10L);
261         event = dispatchSingleEvent(Key.UP, KeyEvent.KEYCODE_ENTER, VehicleDisplay.MAIN,
262                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN);
263 
264         assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_UP);
265         assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER);
266         assertThat(event.getEventTime()).isEqualTo(10L);
267         assertThat(event.getDownTime()).isEqualTo(5L);
268         assertThat(event.getRepeatCount()).isEqualTo(0);
269 
270         when(mUptimeSupplier.getAsLong()).thenReturn(15L);
271         event = dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_ENTER, VehicleDisplay.MAIN,
272                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN);
273 
274         assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_DOWN);
275         assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER);
276         assertThat(event.getEventTime()).isEqualTo(15L);
277         assertThat(event.getDownTime()).isEqualTo(15L);
278         assertThat(event.getRepeatCount()).isEqualTo(0);
279     }
280 
281     /**
282      * Test for handling rotary knob event.
283      */
284     @RequiresDevice
285     @Test
handlesRepeatedKeyWithIndents_anyDisplay()286     public void handlesRepeatedKeyWithIndents_anyDisplay() {
287         subscribeListener();
288         KeyEvent event = dispatchSingleEventWithIndents(KeyEvent.KEYCODE_VOLUME_UP, 5,
289                 VehicleDisplay.MAIN, CarOccupantZoneManager.DISPLAY_TYPE_MAIN);
290         assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_DOWN);
291         assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_VOLUME_UP);
292         assertThat(event.getEventTime()).isEqualTo(0L);
293         assertThat(event.getDownTime()).isEqualTo(0L);
294         assertThat(event.getRepeatCount()).isEqualTo(4);
295 
296         when(mUptimeSupplier.getAsLong()).thenReturn(5L);
297         event = dispatchSingleEventWithIndents(KeyEvent.KEYCODE_VOLUME_UP, 5,
298                 VehicleDisplay.MAIN, CarOccupantZoneManager.DISPLAY_TYPE_MAIN);
299         assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_DOWN);
300         assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_VOLUME_UP);
301         assertThat(event.getEventTime()).isEqualTo(5L);
302         assertThat(event.getDownTime()).isEqualTo(5L);
303         assertThat(event.getRepeatCount()).isEqualTo(9);
304     }
305 
306     @Test
handlesKeyUp_withoutKeyDown_mainDisplay()307     public void handlesKeyUp_withoutKeyDown_mainDisplay() {
308         subscribeListener();
309 
310         when(mUptimeSupplier.getAsLong()).thenReturn(42L);
311         KeyEvent event = dispatchSingleEvent(Key.UP, KeyEvent.KEYCODE_ENTER, VehicleDisplay.MAIN,
312                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN);
313 
314         assertThat(event.getEventTime()).isEqualTo(42L);
315         assertThat(event.getDownTime()).isEqualTo(42L);
316         assertThat(event.getRepeatCount()).isEqualTo(0);
317         assertThat(event.getDisplayId()).isEqualTo(Display.DEFAULT_DISPLAY);
318     }
319 
320     @Test
handlesKeyUp_withoutKeyDown_clusterDisplay()321     public void handlesKeyUp_withoutKeyDown_clusterDisplay() {
322         subscribeListener();
323 
324         when(mUptimeSupplier.getAsLong()).thenReturn(42L);
325         KeyEvent event = dispatchSingleEvent(Key.UP, KeyEvent.KEYCODE_ENTER,
326                 VehicleDisplay.INSTRUMENT_CLUSTER,
327                 CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER);
328 
329         assertThat(event.getEventTime()).isEqualTo(42L);
330         assertThat(event.getDownTime()).isEqualTo(42L);
331         assertThat(event.getRepeatCount()).isEqualTo(0);
332         // event.getDisplayId is not tested since it is assigned by CarInputService
333     }
334 
335     @Test
separateKeyDownEvents_areIndependent_mainDisplay()336     public void separateKeyDownEvents_areIndependent_mainDisplay() {
337         subscribeListener();
338 
339         when(mUptimeSupplier.getAsLong()).thenReturn(27L);
340         dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_ENTER, VehicleDisplay.MAIN,
341                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN);
342 
343         when(mUptimeSupplier.getAsLong()).thenReturn(42L);
344         KeyEvent event = dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_MENU, VehicleDisplay.MAIN,
345                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN);
346 
347         assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_MENU);
348         assertThat(event.getDownTime()).isEqualTo(42L);
349         assertThat(event.getRepeatCount()).isEqualTo(0);
350     }
351 
352     @Test
separateKeyDownEvents_areIndependent_clusterDisplay()353     public void separateKeyDownEvents_areIndependent_clusterDisplay() {
354         subscribeListener();
355 
356         when(mUptimeSupplier.getAsLong()).thenReturn(27L);
357         dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_ENTER, VehicleDisplay.INSTRUMENT_CLUSTER,
358                 CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER);
359 
360         when(mUptimeSupplier.getAsLong()).thenReturn(42L);
361         KeyEvent event = dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_MENU, VehicleDisplay.MAIN,
362                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN);
363 
364         assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_MENU);
365         assertThat(event.getDownTime()).isEqualTo(42L);
366         assertThat(event.getRepeatCount()).isEqualTo(0);
367         // event.getDisplayid is not tested since it is assigned by CarInputService
368     }
369 
370     @Test
dispatchesRotaryEvent_singleVolumeUp_anyDisplay()371     public void dispatchesRotaryEvent_singleVolumeUp_anyDisplay() {
372         subscribeListener();
373 
374         // Arrange mInputListener to capture incoming RotaryEvent
375         List<RotaryEvent> events = new ArrayList<>();
376         doAnswer(invocation -> {
377             RotaryEvent event = invocation.getArgument(0);
378             events.add(event);
379             return null;
380         }).when(mInputListener).onRotaryEvent(any(), eq(CarOccupantZoneManager.DISPLAY_TYPE_MAIN));
381 
382         // Arrange
383         long timestampNanos = 12_345_678_901L;
384 
385         // Act
386         mInputHalService.onHalEvents(ImmutableList.of(
387                 makeRotaryPropValue(RotaryInputType.ROTARY_INPUT_TYPE_AUDIO_VOLUME, 1,
388                         timestampNanos, 0, VehicleDisplay.MAIN)));
389 
390         // Assert
391 
392         // Expected Rotary event to have only one value for uptimeMillisForClicks since the input
393         // property was created with one detent only. This value will correspond to the event
394         // startup time. See CarServiceUtils#getUptimeToElapsedTimeDeltaInMillis for more detailed
395         // information on how this value is calculated.
396         assertThat(events).containsExactly(new RotaryEvent(
397                 /* inputType= */ CarInputManager.INPUT_TYPE_ROTARY_VOLUME,
398                 /* clockwise= */ true,
399                 /* uptimeMillisForClicks= */ new long[]{12345L}));
400     }
401 
402     @Test
dispatchesRotaryEvent_multipleNavigatePrevious_anyDisplay()403     public void dispatchesRotaryEvent_multipleNavigatePrevious_anyDisplay() {
404         subscribeListener();
405 
406         // Arrange mInputListener to capture incoming RotaryEvent
407         List<RotaryEvent> events = new ArrayList<>();
408         doAnswer(invocation -> {
409             RotaryEvent event = invocation.getArgument(0);
410             events.add(event);
411             return null;
412         }).when(mInputListener).onRotaryEvent(any(), eq(CarOccupantZoneManager.DISPLAY_TYPE_MAIN));
413 
414         // Arrange
415         long timestampNanos = 12_345_000_000L;
416         int deltaNanos = 2_000_000;
417         int numberOfDetents = 3;
418 
419         // Act
420         mInputHalService.onHalEvents(ImmutableList.of(
421                 makeRotaryPropValue(RotaryInputType.ROTARY_INPUT_TYPE_SYSTEM_NAVIGATION,
422                         -numberOfDetents, timestampNanos, deltaNanos, VehicleDisplay.MAIN)));
423 
424         // Assert
425 
426         // Expected Rotary event to have 3 values for uptimeMillisForClicks since the input
427         // property value was created with 3 detents. Each value in uptimeMillisForClicks
428         // represents the calculated deltas (in nanoseconds) between pairs of consecutive detents
429         // up times. See InputHalService#dispatchRotaryInput for more detailed information on how
430         // delta times are calculated.
431         assertThat(events).containsExactly(new RotaryEvent(
432                 /* inputType= */CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION,
433                 /* clockwise= */ false,
434                 /* uptimeMillisForClicks= */ new long[]{12345L, 12347L, 12349L}));
435     }
436 
437     @Test
dispatchesCustomInputEvent_mainDisplay()438     public void dispatchesCustomInputEvent_mainDisplay() {
439         // Arrange mInputListener to capture incoming CustomInputEvent
440         subscribeListener();
441 
442         List<CustomInputEvent> events = new ArrayList<>();
443         doAnswer(invocation -> {
444             CustomInputEvent event = invocation.getArgument(0);
445             events.add(event);
446             return null;
447         }).when(mInputListener).onCustomInputEvent(any());
448 
449         // Arrange
450         int repeatCounter = 1;
451         VehiclePropValue customInputPropValue = makeCustomInputPropValue(
452                 CUSTOM_EVENT_F1, VehicleDisplay.MAIN, repeatCounter);
453 
454         // Act
455         mInputHalService.onHalEvents(ImmutableList.of(customInputPropValue));
456 
457         // Assert
458         assertThat(events).containsExactly(new CustomInputEvent(
459                 CustomInputEvent.INPUT_CODE_F1, CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
460                 repeatCounter));
461     }
462 
463     @Test
dispatchesCustomInputEvent_clusterDisplay()464     public void dispatchesCustomInputEvent_clusterDisplay() {
465         // Arrange mInputListener to capture incoming CustomInputEvent
466         subscribeListener();
467 
468         List<CustomInputEvent> events = new ArrayList<>();
469         doAnswer(invocation -> {
470             CustomInputEvent event = invocation.getArgument(0);
471             events.add(event);
472             return null;
473         }).when(mInputListener).onCustomInputEvent(any());
474 
475         // Arrange
476         int repeatCounter = 1;
477         VehiclePropValue customInputPropValue = makeCustomInputPropValue(
478                 CUSTOM_EVENT_F1, VehicleDisplay.INSTRUMENT_CLUSTER, repeatCounter);
479 
480         // Act
481         mInputHalService.onHalEvents(ImmutableList.of(customInputPropValue));
482 
483         // Assert
484         assertThat(events).containsExactly(new CustomInputEvent(
485                 CustomInputEvent.INPUT_CODE_F1,
486                 CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER,
487                 repeatCounter));
488     }
489 
subscribeListener()490     private void subscribeListener() {
491         mInputHalService.takeProperties(ImmutableSet.of(HW_KEY_INPUT_CONFIG));
492         assertThat(mInputHalService.isKeyInputSupported()).isTrue();
493 
494         mInputHalService.setInputListener(mInputListener);
495         verify(mVehicleHal).subscribeProperty(mInputHalService, VehicleProperty.HW_KEY_INPUT);
496     }
497 
dispatchSingleEvent(Key action, int code, int actualDisplay, @DisplayTypeEnum int expectedDisplay)498     private KeyEvent dispatchSingleEvent(Key action, int code, int actualDisplay,
499             @DisplayTypeEnum int expectedDisplay) {
500         ArgumentCaptor<KeyEvent> captor = ArgumentCaptor.forClass(KeyEvent.class);
501         reset(mInputListener);
502         mInputHalService.onHalEvents(
503                 ImmutableList.of(makeKeyPropValue(action, code, actualDisplay)));
504         verify(mInputListener).onKeyEvent(captor.capture(), eq(expectedDisplay));
505         reset(mInputListener);
506         return captor.getValue();
507     }
508 
dispatchSingleEventWithIndents(int code, int indents, int actualDisplay, @DisplayTypeEnum int expectedDisplay)509     private KeyEvent dispatchSingleEventWithIndents(int code, int indents, int actualDisplay,
510             @DisplayTypeEnum int expectedDisplay) {
511         ArgumentCaptor<KeyEvent> captor = ArgumentCaptor.forClass(KeyEvent.class);
512         reset(mInputListener);
513         mInputHalService.onHalEvents(
514                 ImmutableList.of(makeKeyPropValueWithIndents(code, indents, actualDisplay)));
515         verify(mInputListener, times(indents)).onKeyEvent(captor.capture(),
516                 eq(expectedDisplay));
517         reset(mInputListener);
518         return captor.getValue();
519     }
520 
makeKeyPropValue(Key action, int code, @DisplayTypeEnum int targetDisplayType)521     private VehiclePropValue makeKeyPropValue(Key action, int code,
522             @DisplayTypeEnum int targetDisplayType) {
523         VehiclePropValue v = new VehiclePropValue();
524         v.prop = VehicleProperty.HW_KEY_INPUT;
525         v.value.int32Values.add(
526                 (action == Key.DOWN
527                         ? VehicleHwKeyInputAction.ACTION_DOWN
528                         : VehicleHwKeyInputAction.ACTION_UP));
529         v.value.int32Values.add(code);
530         v.value.int32Values.add(targetDisplayType);
531         return v;
532     }
533 
makeKeyPropValueWithIndents(int code, int indents, @DisplayTypeEnum int targetDisplayType)534     private VehiclePropValue makeKeyPropValueWithIndents(int code, int indents,
535             @DisplayTypeEnum int targetDisplayType) {
536         VehiclePropValue v = new VehiclePropValue();
537         v.prop = VehicleProperty.HW_KEY_INPUT;
538         // Only Key.down can have indents.
539         v.value.int32Values.add(VehicleHwKeyInputAction.ACTION_DOWN);
540         v.value.int32Values.add(code);
541         v.value.int32Values.add(targetDisplayType);
542         v.value.int32Values.add(indents);
543         return v;
544     }
545 
makeRotaryPropValue(int rotaryInputType, int detents, long timestamp, int delayBetweenDetents, @DisplayTypeEnum int targetDisplayType)546     private VehiclePropValue makeRotaryPropValue(int rotaryInputType, int detents, long timestamp,
547             int delayBetweenDetents, @DisplayTypeEnum int targetDisplayType) {
548         VehiclePropValue v = new VehiclePropValue();
549         v.prop = VehicleProperty.HW_ROTARY_INPUT;
550         v.value.int32Values.add(rotaryInputType);
551         v.value.int32Values.add(detents);
552         v.value.int32Values.add(targetDisplayType);
553         for (int i = 0; i < Math.abs(detents) - 1; i++) {
554             v.value.int32Values.add(delayBetweenDetents);
555         }
556         v.timestamp = timestamp;
557         return v;
558     }
559 
makeCustomInputPropValue(int inputCode, @DisplayTypeEnum int targetDisplayType, int repeatCounter)560     private VehiclePropValue makeCustomInputPropValue(int inputCode,
561             @DisplayTypeEnum int targetDisplayType, int repeatCounter) {
562         VehiclePropValue v = new VehiclePropValue();
563         v.prop = VehicleProperty.HW_CUSTOM_INPUT;
564         v.value.int32Values.add(inputCode);
565         v.value.int32Values.add(targetDisplayType);
566         v.value.int32Values.add(repeatCounter);
567         return v;
568     }
569 }
570