• 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.CustomInputType.CUSTOM_EVENT_F1;
20 
21 import static com.android.car.CarServiceUtils.toIntArray;
22 
23 import static com.google.common.truth.Truth.assertThat;
24 import static com.google.common.truth.Truth.assertWithMessage;
25 
26 import static org.mockito.ArgumentMatchers.any;
27 import static org.mockito.ArgumentMatchers.anyInt;
28 import static org.mockito.ArgumentMatchers.eq;
29 import static org.mockito.Mockito.atLeastOnce;
30 import static org.mockito.Mockito.doAnswer;
31 import static org.mockito.Mockito.never;
32 import static org.mockito.Mockito.reset;
33 import static org.mockito.Mockito.times;
34 import static org.mockito.Mockito.verify;
35 import static org.mockito.Mockito.when;
36 
37 import static java.util.concurrent.TimeUnit.SECONDS;
38 
39 import android.car.CarOccupantZoneManager;
40 import android.car.input.CarInputManager;
41 import android.car.input.CustomInputEvent;
42 import android.car.input.RotaryEvent;
43 import android.hardware.automotive.vehicle.RotaryInputType;
44 import android.hardware.automotive.vehicle.VehicleDisplay;
45 import android.hardware.automotive.vehicle.VehicleHwKeyInputAction;
46 import android.hardware.automotive.vehicle.VehicleHwMotionButtonStateFlag;
47 import android.hardware.automotive.vehicle.VehicleHwMotionInputAction;
48 import android.hardware.automotive.vehicle.VehicleHwMotionInputSource;
49 import android.hardware.automotive.vehicle.VehicleHwMotionToolType;
50 import android.hardware.automotive.vehicle.VehicleProperty;
51 import android.hardware.automotive.vehicle.VehiclePropertyStatus;
52 import android.view.InputDevice;
53 import android.view.KeyEvent;
54 import android.view.MotionEvent;
55 
56 import androidx.test.filters.RequiresDevice;
57 
58 import com.android.car.hal.test.AidlVehiclePropConfigBuilder;
59 
60 import org.junit.After;
61 import org.junit.Before;
62 import org.junit.Test;
63 import org.junit.runner.RunWith;
64 import org.mockito.ArgumentCaptor;
65 import org.mockito.Mock;
66 import org.mockito.junit.MockitoJUnitRunner;
67 
68 import java.io.PrintWriter;
69 import java.util.ArrayList;
70 import java.util.List;
71 import java.util.Set;
72 import java.util.function.LongSupplier;
73 
74 @RunWith(MockitoJUnitRunner.class)
75 public class InputHalServiceTest {
76     @Mock VehicleHal mVehicleHal;
77     @Mock InputHalService.InputListener mInputListener;
78     @Mock LongSupplier mUptimeSupplier;
79     @Mock PrintWriter mMockPrintWriter;
80 
81     private static final HalPropConfig HW_KEY_INPUT_CONFIG = new AidlHalPropConfig(
82             AidlVehiclePropConfigBuilder.newBuilder(VehicleProperty.HW_KEY_INPUT).build());
83     private static final HalPropConfig HW_ROTARY_INPUT_CONFIG = new AidlHalPropConfig(
84             AidlVehiclePropConfigBuilder.newBuilder(VehicleProperty.HW_ROTARY_INPUT).build());
85     private static final HalPropConfig HW_CUSTOM_INPUT_CONFIG = new AidlHalPropConfig(
86             AidlVehiclePropConfigBuilder.newBuilder(VehicleProperty.HW_CUSTOM_INPUT).build());
87     private static final HalPropConfig HW_MOTION_INPUT = new AidlHalPropConfig(
88             AidlVehiclePropConfigBuilder.newBuilder(VehicleProperty.HW_MOTION_INPUT).build());
89     private static final HalPropConfig HW_KEY_INPUT_V2 = new AidlHalPropConfig(
90             AidlVehiclePropConfigBuilder.newBuilder(VehicleProperty.HW_KEY_INPUT_V2).build());
91 
92 
93     private final HalPropValueBuilder mPropValueBuilder = new HalPropValueBuilder(/*isAidl=*/true);
94 
95     private enum Key {DOWN, UP}
96 
97     private InputHalService mInputHalService;
98 
99     @Before
setUp()100     public void setUp() {
101         when(mUptimeSupplier.getAsLong()).thenReturn(0L);
102         mInputHalService = new InputHalService(mVehicleHal, mUptimeSupplier);
103         mInputHalService.init();
104     }
105 
106     @After
tearDown()107     public void tearDown() {
108         mInputHalService.release();
109         mInputHalService = null;
110     }
111 
112     @Test
ignoresSetListener_beforeKeyInputSupported()113     public void ignoresSetListener_beforeKeyInputSupported() {
114         assertThat(mInputHalService.isKeyInputSupported()).isFalse();
115 
116         mInputHalService.setInputListener(mInputListener);
117 
118         int anyDisplay = VehicleDisplay.MAIN;
119         mInputHalService.onHalEvents(List.of(makeKeyPropValue(Key.DOWN, KeyEvent.KEYCODE_ENTER,
120                 anyDisplay)));
121         verify(mInputListener, never()).onKeyEvent(any(), anyInt());
122     }
123 
124     @Test
takesKeyInputProperty()125     public void takesKeyInputProperty() {
126         Set<HalPropConfig> offeredProps = Set.of(new AidlHalPropConfig(
127                 AidlVehiclePropConfigBuilder.newBuilder(VehicleProperty.ABS_ACTIVE).build()),
128                 HW_KEY_INPUT_CONFIG,
129                 new AidlHalPropConfig(AidlVehiclePropConfigBuilder.newBuilder(
130                         VehicleProperty.CURRENT_GEAR).build()));
131 
132         mInputHalService.takeProperties(offeredProps);
133 
134         assertThat(mInputHalService.isKeyInputSupported()).isTrue();
135         assertThat(mInputHalService.isRotaryInputSupported()).isFalse();
136         assertThat(mInputHalService.isCustomInputSupported()).isFalse();
137     }
138 
139     @Test
takesRotaryInputProperty()140     public void takesRotaryInputProperty() {
141         Set<HalPropConfig> offeredProps = Set.of(
142                 new AidlHalPropConfig(AidlVehiclePropConfigBuilder.newBuilder(
143                         VehicleProperty.ABS_ACTIVE).build()),
144                 HW_ROTARY_INPUT_CONFIG,
145                 new AidlHalPropConfig(AidlVehiclePropConfigBuilder.newBuilder(
146                         VehicleProperty.CURRENT_GEAR).build()));
147 
148         mInputHalService.takeProperties(offeredProps);
149 
150         assertThat(mInputHalService.isRotaryInputSupported()).isTrue();
151         assertThat(mInputHalService.isKeyInputSupported()).isFalse();
152         assertThat(mInputHalService.isCustomInputSupported()).isFalse();
153     }
154 
155     @Test
takesCustomInputProperty()156     public void takesCustomInputProperty() {
157         Set<HalPropConfig> offeredProps = Set.of(
158                 new AidlHalPropConfig(AidlVehiclePropConfigBuilder.newBuilder(
159                         VehicleProperty.ABS_ACTIVE).build()),
160                 HW_CUSTOM_INPUT_CONFIG,
161                 new AidlHalPropConfig(AidlVehiclePropConfigBuilder.newBuilder(
162                         VehicleProperty.CURRENT_GEAR).build()));
163 
164         mInputHalService.takeProperties(offeredProps);
165 
166         assertThat(mInputHalService.isRotaryInputSupported()).isFalse();
167         assertThat(mInputHalService.isKeyInputSupported()).isFalse();
168         assertThat(mInputHalService.isCustomInputSupported()).isTrue();
169         assertThat(mInputHalService.isMotionInputSupported()).isFalse();
170         assertThat(mInputHalService.isKeyInputV2Supported()).isFalse();
171     }
172 
173     @Test
takesKeyAndRotaryAndCustomInputProperty()174     public void takesKeyAndRotaryAndCustomInputProperty() {
175         Set<HalPropConfig> offeredProps = Set.of(
176                 new AidlHalPropConfig(AidlVehiclePropConfigBuilder.newBuilder(
177                         VehicleProperty.ABS_ACTIVE).build()),
178                 HW_KEY_INPUT_CONFIG,
179                 HW_ROTARY_INPUT_CONFIG,
180                 HW_CUSTOM_INPUT_CONFIG,
181                 HW_MOTION_INPUT,
182                 HW_KEY_INPUT_V2,
183                 new AidlHalPropConfig(AidlVehiclePropConfigBuilder.newBuilder(
184                         VehicleProperty.CURRENT_GEAR).build()));
185 
186         mInputHalService.takeProperties(offeredProps);
187 
188         assertThat(mInputHalService.isKeyInputSupported()).isTrue();
189         assertThat(mInputHalService.isRotaryInputSupported()).isTrue();
190         assertThat(mInputHalService.isCustomInputSupported()).isTrue();
191         assertThat(mInputHalService.isKeyInputV2Supported()).isTrue();
192         assertThat(mInputHalService.isMotionInputSupported()).isTrue();
193     }
194 
195     @Test
dispatchesInputEvent_single_toListener_mainDisplay()196     public void dispatchesInputEvent_single_toListener_mainDisplay() {
197         subscribeListener();
198 
199         KeyEvent event = dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_ENTER, VehicleDisplay.MAIN,
200                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN);
201         assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_DOWN);
202         assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER);
203     }
204 
205     @Test
dispatchesInputEvent_single_toListener_clusterDisplay()206     public void dispatchesInputEvent_single_toListener_clusterDisplay() {
207         subscribeListener();
208 
209         KeyEvent event = dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_ENTER,
210                 VehicleDisplay.INSTRUMENT_CLUSTER,
211                 CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER);
212         assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_DOWN);
213         assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER);
214     }
215 
216     @Test
dispatchesInputEvent_multiple_toListener_mainDisplay()217     public void dispatchesInputEvent_multiple_toListener_mainDisplay() {
218         subscribeListener();
219 
220         // KeyEvents get recycled, so we can't just use ArgumentCaptor#getAllValues here.
221         // We need to make a copy of the information we need at the time of the call.
222         List<KeyEvent> events = new ArrayList<>();
223         doAnswer(inv -> {
224             KeyEvent event = inv.getArgument(0);
225             events.add(event.copy());
226             return null;
227         }).when(mInputListener).onKeyEvent(any(), eq(CarOccupantZoneManager.DISPLAY_TYPE_MAIN));
228 
229         mInputHalService.onHalEvents(
230                 List.of(
231                         makeKeyPropValue(Key.DOWN, KeyEvent.KEYCODE_ENTER, VehicleDisplay.MAIN),
232                         makeKeyPropValue(Key.DOWN, KeyEvent.KEYCODE_MENU, VehicleDisplay.MAIN)));
233 
234         assertThat(events.get(0).getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER);
235         assertThat(events.get(1).getKeyCode()).isEqualTo(KeyEvent.KEYCODE_MENU);
236 
237         events.forEach(KeyEvent::recycle);
238     }
239 
240     @Test
dispatchesInputEvent_multiple_toListener_clusterDisplay()241     public void dispatchesInputEvent_multiple_toListener_clusterDisplay() {
242         subscribeListener();
243 
244         // KeyEvents get recycled, so we can't just use ArgumentCaptor#getAllValues here.
245         // We need to make a copy of the information we need at the time of the call.
246         List<KeyEvent> events = new ArrayList<>();
247         doAnswer(inv -> {
248             KeyEvent event = inv.getArgument(0);
249             events.add(event.copy());
250             return null;
251         }).when(mInputListener).onKeyEvent(any(),
252                 eq(CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER));
253 
254         mInputHalService.onHalEvents(
255                 List.of(
256                         makeKeyPropValue(Key.DOWN, KeyEvent.KEYCODE_ENTER,
257                                 VehicleDisplay.INSTRUMENT_CLUSTER),
258                         makeKeyPropValue(Key.DOWN, KeyEvent.KEYCODE_MENU,
259                                 VehicleDisplay.INSTRUMENT_CLUSTER)));
260 
261         assertThat(events.get(0).getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER);
262         assertThat(events.get(1).getKeyCode()).isEqualTo(KeyEvent.KEYCODE_MENU);
263 
264         events.forEach(KeyEvent::recycle);
265     }
266 
267     @Test
dispatchesInputEvent_invalidInputEvent()268     public void dispatchesInputEvent_invalidInputEvent() {
269         subscribeListener();
270         HalPropValue v = mPropValueBuilder.build(VehicleProperty.HW_KEY_INPUT, /* areaId= */ 0);
271         // Missing action, code, display_type.
272         mInputHalService.onHalEvents(List.of(v));
273         verify(mInputListener, never()).onKeyEvent(any(),  anyInt());
274 
275         // Missing code, display_type.
276         v = mPropValueBuilder.build(VehicleProperty.HW_KEY_INPUT, /* areaId= */ 0,
277                 VehicleHwKeyInputAction.ACTION_DOWN);
278         mInputHalService.onHalEvents(List.of(v));
279         verify(mInputListener, never()).onKeyEvent(any(),  anyInt());
280 
281         // Missing display_type.
282         v = mPropValueBuilder.build(VehicleProperty.HW_KEY_INPUT, /* areaId= */ 0,
283                 new int[]{VehicleHwKeyInputAction.ACTION_DOWN, KeyEvent.KEYCODE_ENTER});
284         mInputHalService.onHalEvents(List.of(v));
285         verify(mInputListener, never()).onKeyEvent(any(),  anyInt());
286     }
287 
288     @Test
handlesRepeatedKeys_anyDisplay()289     public void handlesRepeatedKeys_anyDisplay() {
290         subscribeListener();
291 
292         KeyEvent event = dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_ENTER, VehicleDisplay.MAIN,
293                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN);
294         assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_DOWN);
295         assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER);
296         assertThat(event.getEventTime()).isEqualTo(0L);
297         assertThat(event.getDownTime()).isEqualTo(0L);
298         assertThat(event.getRepeatCount()).isEqualTo(0);
299 
300         when(mUptimeSupplier.getAsLong()).thenReturn(5L);
301         event = dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_ENTER, VehicleDisplay.MAIN,
302                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN);
303 
304         assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_DOWN);
305         assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER);
306         assertThat(event.getEventTime()).isEqualTo(5L);
307         assertThat(event.getDownTime()).isEqualTo(5L);
308         assertThat(event.getRepeatCount()).isEqualTo(1);
309 
310         when(mUptimeSupplier.getAsLong()).thenReturn(10L);
311         event = dispatchSingleEvent(Key.UP, KeyEvent.KEYCODE_ENTER, VehicleDisplay.MAIN,
312                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN);
313 
314         assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_UP);
315         assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER);
316         assertThat(event.getEventTime()).isEqualTo(10L);
317         assertThat(event.getDownTime()).isEqualTo(5L);
318         assertThat(event.getRepeatCount()).isEqualTo(0);
319 
320         when(mUptimeSupplier.getAsLong()).thenReturn(15L);
321         event = dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_ENTER, VehicleDisplay.MAIN,
322                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN);
323 
324         assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_DOWN);
325         assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER);
326         assertThat(event.getEventTime()).isEqualTo(15L);
327         assertThat(event.getDownTime()).isEqualTo(15L);
328         assertThat(event.getRepeatCount()).isEqualTo(0);
329     }
330 
331     /**
332      * Test for handling rotary knob event.
333      */
334     @RequiresDevice
335     @Test
handlesRepeatedKeyWithIndents_anyDisplay()336     public void handlesRepeatedKeyWithIndents_anyDisplay() {
337         subscribeListener();
338         KeyEvent event = dispatchSingleEventWithIndents(KeyEvent.KEYCODE_VOLUME_UP, 5,
339                 VehicleDisplay.MAIN, CarOccupantZoneManager.DISPLAY_TYPE_MAIN);
340         assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_DOWN);
341         assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_VOLUME_UP);
342         assertThat(event.getEventTime()).isEqualTo(0L);
343         assertThat(event.getDownTime()).isEqualTo(0L);
344         assertThat(event.getRepeatCount()).isEqualTo(4);
345 
346         when(mUptimeSupplier.getAsLong()).thenReturn(5L);
347         event = dispatchSingleEventWithIndents(KeyEvent.KEYCODE_VOLUME_UP, 5,
348                 VehicleDisplay.MAIN, CarOccupantZoneManager.DISPLAY_TYPE_MAIN);
349         assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_DOWN);
350         assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_VOLUME_UP);
351         assertThat(event.getEventTime()).isEqualTo(5L);
352         assertThat(event.getDownTime()).isEqualTo(5L);
353         assertThat(event.getRepeatCount()).isEqualTo(9);
354     }
355 
356     @Test
handlesKeyUp_withoutKeyDown_mainDisplay()357     public void handlesKeyUp_withoutKeyDown_mainDisplay() {
358         subscribeListener();
359 
360         when(mUptimeSupplier.getAsLong()).thenReturn(42L);
361         KeyEvent event = dispatchSingleEvent(Key.UP, KeyEvent.KEYCODE_ENTER, VehicleDisplay.MAIN,
362                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN);
363 
364         assertThat(event.getEventTime()).isEqualTo(42L);
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
handlesKeyUp_withoutKeyDown_clusterDisplay()371     public void handlesKeyUp_withoutKeyDown_clusterDisplay() {
372         subscribeListener();
373 
374         when(mUptimeSupplier.getAsLong()).thenReturn(42L);
375         KeyEvent event = dispatchSingleEvent(Key.UP, KeyEvent.KEYCODE_ENTER,
376                 VehicleDisplay.INSTRUMENT_CLUSTER,
377                 CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER);
378 
379         assertThat(event.getEventTime()).isEqualTo(42L);
380         assertThat(event.getDownTime()).isEqualTo(42L);
381         assertThat(event.getRepeatCount()).isEqualTo(0);
382         // event.getDisplayId is not tested since it is assigned by CarInputService
383     }
384 
385     @Test
separateKeyDownEvents_areIndependent_mainDisplay()386     public void separateKeyDownEvents_areIndependent_mainDisplay() {
387         subscribeListener();
388 
389         when(mUptimeSupplier.getAsLong()).thenReturn(27L);
390         dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_ENTER, VehicleDisplay.MAIN,
391                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN);
392 
393         when(mUptimeSupplier.getAsLong()).thenReturn(42L);
394         KeyEvent event = dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_MENU, VehicleDisplay.MAIN,
395                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN);
396 
397         assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_MENU);
398         assertThat(event.getDownTime()).isEqualTo(42L);
399         assertThat(event.getRepeatCount()).isEqualTo(0);
400     }
401 
402     @Test
separateKeyDownEvents_areIndependent_clusterDisplay()403     public void separateKeyDownEvents_areIndependent_clusterDisplay() {
404         subscribeListener();
405 
406         when(mUptimeSupplier.getAsLong()).thenReturn(27L);
407         dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_ENTER, VehicleDisplay.INSTRUMENT_CLUSTER,
408                 CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER);
409 
410         when(mUptimeSupplier.getAsLong()).thenReturn(42L);
411         KeyEvent event = dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_MENU, VehicleDisplay.MAIN,
412                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN);
413 
414         assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_MENU);
415         assertThat(event.getDownTime()).isEqualTo(42L);
416         assertThat(event.getRepeatCount()).isEqualTo(0);
417         // event.getDisplayid is not tested since it is assigned by CarInputService
418     }
419 
420     @Test
dispatchesRotaryEvent_singleVolumeUp_anyDisplay()421     public void dispatchesRotaryEvent_singleVolumeUp_anyDisplay() {
422         subscribeListener();
423 
424         // Arrange mInputListener to capture incoming RotaryEvent
425         List<RotaryEvent> events = new ArrayList<>();
426         doAnswer(invocation -> {
427             RotaryEvent event = invocation.getArgument(0);
428             events.add(event);
429             return null;
430         }).when(mInputListener).onRotaryEvent(any(), eq(CarOccupantZoneManager.DISPLAY_TYPE_MAIN));
431 
432         // Arrange
433         long timestampNanos = 12_345_678_901L;
434 
435         // Act
436         mInputHalService.onHalEvents(List.of(
437                 makeRotaryPropValue(RotaryInputType.ROTARY_INPUT_TYPE_AUDIO_VOLUME, 1,
438                         timestampNanos, 0, VehicleDisplay.MAIN)));
439 
440         // Assert
441 
442         // Expected Rotary event to have only one value for uptimeMillisForClicks since the input
443         // property was created with one detent only. This value will correspond to the event
444         // startup time. See CarServiceUtils#getUptimeToElapsedTimeDeltaInMillis for more detailed
445         // information on how this value is calculated.
446         assertThat(events).containsExactly(new RotaryEvent(
447                 /* inputType= */ CarInputManager.INPUT_TYPE_ROTARY_VOLUME,
448                 /* clockwise= */ true,
449                 /* uptimeMillisForClicks= */ new long[]{12345L}));
450     }
451 
452     @Test
dispatchesRotaryEvent_multipleNavigatePrevious_anyDisplay()453     public void dispatchesRotaryEvent_multipleNavigatePrevious_anyDisplay() {
454         subscribeListener();
455 
456         // Arrange mInputListener to capture incoming RotaryEvent
457         List<RotaryEvent> events = new ArrayList<>();
458         doAnswer(invocation -> {
459             RotaryEvent event = invocation.getArgument(0);
460             events.add(event);
461             return null;
462         }).when(mInputListener).onRotaryEvent(any(), eq(CarOccupantZoneManager.DISPLAY_TYPE_MAIN));
463 
464         // Arrange
465         long timestampNanos = 12_345_000_000L;
466         int deltaNanos = 2_000_000;
467         int numberOfDetents = 3;
468 
469         // Act
470         mInputHalService.onHalEvents(List.of(
471                 makeRotaryPropValue(RotaryInputType.ROTARY_INPUT_TYPE_SYSTEM_NAVIGATION,
472                         -numberOfDetents, timestampNanos, deltaNanos, VehicleDisplay.MAIN)));
473 
474         // Assert
475 
476         // Expected Rotary event to have 3 values for uptimeMillisForClicks since the input
477         // property value was created with 3 detents. Each value in uptimeMillisForClicks
478         // represents the calculated deltas (in nanoseconds) between pairs of consecutive detents
479         // up times. See InputHalService#dispatchRotaryInput for more detailed information on how
480         // delta times are calculated.
481         assertThat(events).containsExactly(new RotaryEvent(
482                 /* inputType= */CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION,
483                 /* clockwise= */ false,
484                 /* uptimeMillisForClicks= */ new long[]{12345L, 12347L, 12349L}));
485     }
486 
487     @Test
dispatchesRotaryEvent_invalidInputEvent()488     public void dispatchesRotaryEvent_invalidInputEvent() {
489         subscribeListener();
490         HalPropValue v = mPropValueBuilder.build(VehicleProperty.HW_ROTARY_INPUT, /* areaId= */ 0);
491 
492         // Missing rotaryInputType, detentCount, targetDisplayType.
493         mInputHalService.onHalEvents(List.of(v));
494         verify(mInputListener, never()).onRotaryEvent(any(),  anyInt());
495 
496         // Missing detentCount, targetDisplayType.
497         v = mPropValueBuilder.build(VehicleProperty.HW_ROTARY_INPUT, /* areaId= */ 0,
498                 RotaryInputType.ROTARY_INPUT_TYPE_SYSTEM_NAVIGATION);
499         mInputHalService.onHalEvents(List.of(v));
500         verify(mInputListener, never()).onRotaryEvent(any(),  anyInt());
501 
502         // Missing targetDisplayType.
503         v = mPropValueBuilder.build(VehicleProperty.HW_ROTARY_INPUT, /* areaId= */ 0,
504                 new int[]{RotaryInputType.ROTARY_INPUT_TYPE_SYSTEM_NAVIGATION, 1});
505         mInputHalService.onHalEvents(List.of(v));
506         verify(mInputListener, never()).onRotaryEvent(any(),  anyInt());
507 
508         // Add targetDisplayType and set detentCount to 0.
509         v = mPropValueBuilder.build(VehicleProperty.HW_ROTARY_INPUT, /* areaId= */ 0,
510                 new int[]{RotaryInputType.ROTARY_INPUT_TYPE_SYSTEM_NAVIGATION, 0,
511                         VehicleDisplay.MAIN});
512         mInputHalService.onHalEvents(List.of(v));
513         verify(mInputListener, never()).onRotaryEvent(any(),  anyInt());
514 
515         // Set detentCount to 1.
516         // Add additional unnecessary arguments so that the array size does not match detentCount.
517         v = mPropValueBuilder.build(VehicleProperty.HW_ROTARY_INPUT, /* areaId= */ 1,
518                 new int[]{RotaryInputType.ROTARY_INPUT_TYPE_SYSTEM_NAVIGATION, 1,
519                         VehicleDisplay.MAIN, 0});
520         mInputHalService.onHalEvents(List.of(v));
521         verify(mInputListener, never()).onRotaryEvent(any(),  anyInt());
522 
523         // Set invalid detentCount.
524         v = mPropValueBuilder.build(VehicleProperty.HW_ROTARY_INPUT, /* areaId= */ 1,
525                 new int[]{RotaryInputType.ROTARY_INPUT_TYPE_SYSTEM_NAVIGATION, Integer.MAX_VALUE,
526                         VehicleDisplay.MAIN, 0});
527         mInputHalService.onHalEvents(List.of(v));
528         verify(mInputListener, never()).onRotaryEvent(any(),  anyInt());
529 
530         // Set invalid detentCount.
531         v = mPropValueBuilder.build(VehicleProperty.HW_ROTARY_INPUT, /* areaId= */ 1,
532                 new int[]{RotaryInputType.ROTARY_INPUT_TYPE_SYSTEM_NAVIGATION, Integer.MIN_VALUE,
533                         VehicleDisplay.MAIN, 0});
534         mInputHalService.onHalEvents(List.of(v));
535         verify(mInputListener, never()).onRotaryEvent(any(),  anyInt());
536     }
537 
538     @Test
dispatchesCustomInputEvent_mainDisplay()539     public void dispatchesCustomInputEvent_mainDisplay() {
540         // Arrange mInputListener to capture incoming CustomInputEvent
541         subscribeListener();
542 
543         List<CustomInputEvent> events = new ArrayList<>();
544         doAnswer(invocation -> {
545             CustomInputEvent event = invocation.getArgument(0);
546             events.add(event);
547             return null;
548         }).when(mInputListener).onCustomInputEvent(any());
549 
550         // Arrange
551         int repeatCounter = 1;
552         HalPropValue customInputPropValue = makeCustomInputPropValue(
553                 CUSTOM_EVENT_F1, VehicleDisplay.MAIN, repeatCounter);
554 
555         // Act
556         mInputHalService.onHalEvents(List.of(customInputPropValue));
557 
558         // Assert
559         assertThat(events).containsExactly(new CustomInputEvent(
560                 CustomInputEvent.INPUT_CODE_F1, CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
561                 repeatCounter));
562     }
563 
564     @Test
dispatchesCustomInputEvent_clusterDisplay()565     public void dispatchesCustomInputEvent_clusterDisplay() {
566         // Arrange mInputListener to capture incoming CustomInputEvent
567         subscribeListener();
568 
569         List<CustomInputEvent> events = new ArrayList<>();
570         doAnswer(invocation -> {
571             CustomInputEvent event = invocation.getArgument(0);
572             events.add(event);
573             return null;
574         }).when(mInputListener).onCustomInputEvent(any());
575 
576         // Arrange
577         int repeatCounter = 1;
578         HalPropValue customInputPropValue = makeCustomInputPropValue(
579                 CUSTOM_EVENT_F1, VehicleDisplay.INSTRUMENT_CLUSTER, repeatCounter);
580 
581         // Act
582         mInputHalService.onHalEvents(List.of(customInputPropValue));
583 
584         // Assert
585         assertThat(events).containsExactly(new CustomInputEvent(
586                 CustomInputEvent.INPUT_CODE_F1,
587                 CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER,
588                 repeatCounter));
589     }
590 
591     @Test
dispatchesCustomInputEvent_InvalidEvent()592     public void dispatchesCustomInputEvent_InvalidEvent() {
593         // Arrange mInputListener to capture incoming CustomInputEvent
594         subscribeListener();
595 
596         HalPropValue v = mPropValueBuilder.build(VehicleProperty.HW_CUSTOM_INPUT, /* areaId= */ 0);
597 
598         // Missing inputCode, targetDisplayType, repeatCounter.
599         mInputHalService.onHalEvents(List.of(v));
600         verify(mInputListener, never()).onCustomInputEvent(any());
601 
602         // Missing targetDisplayType, repeatCounter.
603         v = mPropValueBuilder.build(VehicleProperty.HW_CUSTOM_INPUT, /* areaId= */ 0,
604                 CustomInputEvent.INPUT_CODE_F1);
605         mInputHalService.onHalEvents(List.of(v));
606         verify(mInputListener, never()).onCustomInputEvent(any());
607 
608         // Missing repeatCounter.
609         v = mPropValueBuilder.build(VehicleProperty.HW_CUSTOM_INPUT, /* areaId= */ 0,
610                 new int[]{CustomInputEvent.INPUT_CODE_F1,
611                         CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER});
612         mInputHalService.onHalEvents(List.of(v));
613 
614         verify(mInputListener, never()).onCustomInputEvent(any());
615     }
616 
617     @Test
dispatchesCustomInputEvent_acceptInputCodeHigherThanF10()618     public void dispatchesCustomInputEvent_acceptInputCodeHigherThanF10() {
619         // Custom Input Events may accept input code values outside the
620         // CUSTOM_EVENT_F1 to F10 range.
621         int someInputCodeValueHigherThanF10 = 1000;
622 
623         // Arrange mInputListener to capture incoming CustomInputEvent
624         subscribeListener();
625 
626         List<CustomInputEvent> events = new ArrayList<>();
627         doAnswer(invocation -> {
628             CustomInputEvent event = invocation.getArgument(0);
629             events.add(event);
630             return null;
631         }).when(mInputListener).onCustomInputEvent(any());
632 
633         // Arrange
634         int repeatCounter = 1;
635         HalPropValue customInputPropValue = makeCustomInputPropValue(
636                 someInputCodeValueHigherThanF10, VehicleDisplay.MAIN, repeatCounter);
637 
638         // Act
639         mInputHalService.onHalEvents(List.of(customInputPropValue));
640 
641         // Assert
642         assertThat(events).containsExactly(new CustomInputEvent(
643                 someInputCodeValueHigherThanF10, CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
644                 repeatCounter));
645     }
646 
647     @Test
dispatchesKeyInputEvent_perSeat_anyDisplay()648     public void dispatchesKeyInputEvent_perSeat_anyDisplay() {
649         // Arrange
650         subscribeListener();
651 
652         // Act
653         KeyEvent event = dispatchSinglePerSeatEvent(
654                 /* seat= */ 1,
655                 VehicleDisplay.MAIN,
656                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
657                 KeyEvent.KEYCODE_HOME,
658                 Key.UP,
659                 /* repeatCount= */ 1,
660                 /* downTime= */ SECONDS.toNanos(7),
661                 /* timeStampNanos= */ SECONDS.toNanos(8)
662         );
663 
664         // Assert
665         assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_HOME);
666         assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_UP);
667         assertThat(event.getRepeatCount()).isEqualTo(1);
668         assertThat(event.getDownTime()).isEqualTo(SECONDS.toMillis(7));
669         assertThat(event.getEventTime()).isEqualTo(SECONDS.toMillis(8));
670     }
671 
672     @Test
dispatchesKeyInputEvent_withSameEventTimeAndDownTime_perSeat_whenKeyDown()673     public void dispatchesKeyInputEvent_withSameEventTimeAndDownTime_perSeat_whenKeyDown() {
674         // Arrange
675         subscribeListener();
676 
677         // Act
678         KeyEvent event = dispatchSinglePerSeatEvent(
679                 /* seat= */ 1,
680                 VehicleDisplay.MAIN,
681                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
682                 KeyEvent.KEYCODE_HOME,
683                 Key.DOWN,
684                 /* repeatCount= */ 1,
685                 /* downTime= */ SECONDS.toNanos(7),
686                 /* timeStampNanos= */ SECONDS.toNanos(8)
687         );
688 
689         // Assert
690         assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_HOME);
691         assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_DOWN);
692         assertThat(event.getRepeatCount()).isEqualTo(1);
693         assertThat(event.getDownTime()).isEqualTo(SECONDS.toMillis(7));
694         assertThat(event.getEventTime()).isEqualTo(SECONDS.toMillis(7));
695     }
696 
697     @Test
dispatchesMotionInputEvent_perSeat_anyDisplay()698     public void dispatchesMotionInputEvent_perSeat_anyDisplay() {
699         // Arrange
700         subscribeListener();
701 
702         // Act
703         MotionEvent event = dispatchSinglePerSeatMotionEvent(
704                 /* seat= */ 1,
705                 VehicleDisplay.MAIN,
706                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
707                 VehicleHwMotionInputSource.SOURCE_TOUCHSCREEN,
708                 VehicleHwMotionInputAction.ACTION_DOWN,
709                 VehicleHwMotionButtonStateFlag.BUTTON_PRIMARY,
710                 /* pointerCount= */ 2,
711                 /* pointerIds= */ new int[] {10, 20},
712                 /* toolTypes= */ new int[] {VehicleHwMotionToolType.TOOL_TYPE_FINGER,
713                         VehicleHwMotionToolType.TOOL_TYPE_MOUSE},
714                 /* xData= */ new float[] {100, 200},
715                 /* yData= */ new float[] {300, 400},
716                 /* pressureData= */ new float[] {1, 2},
717                 /* sizeData= */ new float[] {3, 4},
718                 /* downTimeNanos= */ SECONDS.toNanos(7),
719                 /* timeStampNanos= */ SECONDS.toNanos(8)
720                 );
721 
722         // Assert
723         assertThat(event.getSource()).isEqualTo(InputDevice.SOURCE_TOUCHSCREEN);
724         assertThat(event.getAction()).isEqualTo(MotionEvent.ACTION_DOWN);
725         assertThat(event.getButtonState()).isEqualTo(MotionEvent.BUTTON_PRIMARY);
726         assertThat(event.getPointerCount()).isEqualTo(2);
727         assertThat(event.getPointerId(0)).isEqualTo(10);
728         assertThat(event.getPointerId(1)).isEqualTo(20);
729         assertThat(event.getToolType(0)).isEqualTo(MotionEvent.TOOL_TYPE_FINGER);
730         assertThat(event.getToolType(1)).isEqualTo(MotionEvent.TOOL_TYPE_MOUSE);
731         assertThat(event.getX(0)).isEqualTo(100);
732         assertThat(event.getX(1)).isEqualTo(200);
733         assertThat(event.getY(0)).isEqualTo(300);
734         assertThat(event.getY(1)).isEqualTo(400);
735         assertThat(event.getPressure(0)).isEqualTo(1);
736         assertThat(event.getPressure(1)).isEqualTo(2);
737         assertThat(event.getSize(0)).isEqualTo(3);
738         assertThat(event.getSize(1)).isEqualTo(4);
739         assertThat(event.getDownTime()).isEqualTo(SECONDS.toMillis(7));
740         assertThat(event.getEventTime()).isEqualTo(SECONDS.toMillis(8));
741     }
742 
743     @Test
dump_withNoLastKeyEvent_printsEmpty()744     public void dump_withNoLastKeyEvent_printsEmpty() {
745         // Arrange
746         ArgumentCaptor<String> stringArgumentCaptor = ArgumentCaptor.forClass(String.class);
747 
748         // Act
749         mInputHalService.dump(mMockPrintWriter);
750 
751         // Assert
752         verify(mMockPrintWriter, atLeastOnce()).println(stringArgumentCaptor.capture());
753         List<String> strings = stringArgumentCaptor.getAllValues();
754         assertThat(strings.get(5)).startsWith("mLastFewDispatchedV2KeyEvents:");
755     }
756 
757     @Test
dump_containsLastFewKeyEvents()758     public void dump_containsLastFewKeyEvents() {
759         // Arrange
760         subscribeListener();
761         ArgumentCaptor<String> stringArgumentCaptor = ArgumentCaptor.forClass(String.class);
762         dispatchMultipleV2KeyEvents(KeyEvent.KEYCODE_HOME, /* times= */ 1);
763         dispatchMultipleV2KeyEvents(KeyEvent.KEYCODE_BACK, /* times= */ 9);
764         dispatchMultipleV2KeyEvents(KeyEvent.KEYCODE_VOLUME_UP, /* times= */ 1);
765 
766         // Act
767         mInputHalService.dump(mMockPrintWriter);
768 
769         // Assert
770         verify(mMockPrintWriter, atLeastOnce()).println(stringArgumentCaptor.capture());
771         List<String> strings = stringArgumentCaptor.getAllValues();
772         int numHomeEvents = 0;
773         int numBackEvents = 0;
774         int numVolUpEvents = 0;
775         assertThat(strings.get(5)).startsWith("mLastFewDispatchedV2KeyEvents:");
776         for (int i = 5; i < strings.size(); i++) {
777             if (strings.get(i).contains("keyCode=KEYCODE_HOME")) {
778                 numHomeEvents++;
779             } else if (strings.get(i).contains("keyCode=KEYCODE_BACK")) {
780                 numBackEvents++;
781             } else if (strings.get(i).contains("keyCode=KEYCODE_VOLUME_UP")) {
782                 numVolUpEvents++;
783             }
784         }
785         assertWithMessage("Number of home events").that(numHomeEvents).isEqualTo(0);
786         assertWithMessage("Number of back events").that(numBackEvents).isEqualTo(9);
787         assertWithMessage("Number of volume up events").that(numVolUpEvents).isEqualTo(1);
788     }
789 
790     @Test
dump_containsLastFewMotionEvents()791     public void dump_containsLastFewMotionEvents() {
792         // Arrange
793         subscribeListener();
794         ArgumentCaptor<String> stringArgumentCaptor = ArgumentCaptor.forClass(String.class);
795         dispatchMultipleMotionEvents(VehicleHwMotionInputAction.ACTION_DOWN, /* times= */ 1);
796         dispatchMultipleMotionEvents(VehicleHwMotionInputAction.ACTION_MOVE, /* times= */ 9);
797         dispatchMultipleMotionEvents(VehicleHwMotionInputAction.ACTION_UP, /* times= */ 1);
798 
799         // Act
800         mInputHalService.dump(mMockPrintWriter);
801 
802         // Assert
803         verify(mMockPrintWriter, atLeastOnce()).println(stringArgumentCaptor.capture());
804         List<String> strings = stringArgumentCaptor.getAllValues();
805         int numMoveEvents = 0;
806         int numUpEvents = 0;
807         int numDownEvents = 0;
808         assertThat(strings.get(6)).startsWith("mLastFewDispatchedMotionEvents:");
809         for (int i = 6; i < strings.size(); i++) {
810             if (strings.get(i).contains("action=ACTION_DOWN")) {
811                 numDownEvents++;
812             } else if (strings.get(i).contains("action=ACTION_MOVE")) {
813                 numMoveEvents++;
814             } else if (strings.get(i).contains("action=ACTION_UP")) {
815                 numUpEvents++;
816             }
817         }
818         // Number of down events should be 0 as only 10 recent events are stored.
819         assertWithMessage("Number of down events").that(numDownEvents).isEqualTo(0);
820         assertWithMessage("Number of move events").that(numMoveEvents).isEqualTo(9);
821         assertWithMessage("Number of up events").that(numUpEvents).isEqualTo(1);
822     }
823 
dispatchMultipleMotionEvents(@ehicleHwMotionInputAction int action, int times)824     private void dispatchMultipleMotionEvents(@VehicleHwMotionInputAction int action, int times) {
825         for (int i = 0; i < times; i++) {
826             dispatchSinglePerSeatMotionEvent(
827                     /* seat= */ 1,
828                     VehicleDisplay.MAIN,
829                     CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
830                     VehicleHwMotionInputSource.SOURCE_TOUCHSCREEN,
831                     action,
832                     VehicleHwMotionButtonStateFlag.BUTTON_PRIMARY,
833                     /* pointerCount= */ 2,
834                     /* pointerIds= */ new int[]{10, 20},
835                     /* toolTypes= */ new int[]{VehicleHwMotionToolType.TOOL_TYPE_FINGER,
836                             VehicleHwMotionToolType.TOOL_TYPE_MOUSE},
837                     /* xData= */ new float[]{100, 200},
838                     /* yData= */ new float[]{300, 400},
839                     /* pressureData= */ new float[]{1, 2},
840                     /* sizeData= */ new float[]{3, 4},
841                     /* downTimeNanos= */ SECONDS.toNanos(7),
842                     /* timeStampNanos= */ SECONDS.toNanos(8)
843             );
844         }
845     }
846 
dispatchMultipleV2KeyEvents(int keyCode, int times)847     private void dispatchMultipleV2KeyEvents(int keyCode, int times) {
848         for (int i = 0; i < times; i++) {
849             dispatchSinglePerSeatEvent(
850                     /* seat= */ 1,
851                     VehicleDisplay.MAIN,
852                     CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
853                     keyCode,
854                     Key.DOWN,
855                     /* repeatCount= */ 1,
856                     /* downTime= */ SECONDS.toNanos(7),
857                     /* timeStampNanos= */ SECONDS.toNanos(9)
858             );
859         }
860     }
861 
subscribeListener()862     private void subscribeListener() {
863         mInputHalService.takeProperties(Set.of(HW_KEY_INPUT_CONFIG));
864         assertThat(mInputHalService.isKeyInputSupported()).isTrue();
865 
866         mInputHalService.setInputListener(mInputListener);
867         verify(mVehicleHal).subscribePropertySafe(mInputHalService, VehicleProperty.HW_KEY_INPUT);
868     }
869 
dispatchSingleEvent(Key action, int code, int actualDisplay, @DisplayTypeEnum int expectedDisplay)870     private KeyEvent dispatchSingleEvent(Key action, int code, int actualDisplay,
871             @DisplayTypeEnum int expectedDisplay) {
872         ArgumentCaptor<KeyEvent> captor = ArgumentCaptor.forClass(KeyEvent.class);
873         reset(mInputListener);
874         mInputHalService.onHalEvents(
875                 List.of(makeKeyPropValue(action, code, actualDisplay)));
876         verify(mInputListener).onKeyEvent(captor.capture(), eq(expectedDisplay));
877         reset(mInputListener);
878         return captor.getValue();
879     }
880 
dispatchSinglePerSeatEvent(int seat, int actualDisplay, @DisplayTypeEnum int expectedDisplay, int code, Key action, int repeatCount, long downTime, long timeStampNanos)881     private KeyEvent dispatchSinglePerSeatEvent(int seat, int actualDisplay,
882             @DisplayTypeEnum int expectedDisplay, int code, Key action, int repeatCount,
883             long downTime, long timeStampNanos) {
884         ArgumentCaptor<KeyEvent> captor = ArgumentCaptor.forClass(KeyEvent.class);
885         reset(mInputListener);
886 
887         int actionValue = (action == Key.DOWN
888                 ? VehicleHwKeyInputAction.ACTION_DOWN
889                 : VehicleHwKeyInputAction.ACTION_UP);
890         mInputHalService.onHalEvents(
891                 List.of(mPropValueBuilder.build(VehicleProperty.HW_KEY_INPUT_V2,
892                         /* areaId= */ seat,
893                         /* timestamp= */ timeStampNanos,
894                         VehiclePropertyStatus.AVAILABLE,
895                         /* int32Values= */ new int[] {actualDisplay, code, actionValue,
896                                 repeatCount},
897                         /* floatValues= */ new float[] {},
898                         /* int64Values= */ new long[] {downTime},
899                         /* stringValue= */ "",
900                         /* byteValues= */ new byte[] {}
901                 )));
902         verify(mInputListener).onKeyEvent(captor.capture(), eq(expectedDisplay),
903                 eq(seat));
904         reset(mInputListener);
905         return captor.getValue();
906     }
907 
dispatchSinglePerSeatMotionEvent(int seat, int actualDisplay, @DisplayTypeEnum int expectedDisplay, int inputSource, int motionActionCode, int buttonState, int pointerCount, int[] pointerIds, int[] toolTypes, float[] xData, float[] yData, float[] pressureData, float[] sizeData, long downTimeNanos, long timeStampNanos)908     private MotionEvent dispatchSinglePerSeatMotionEvent(int seat, int actualDisplay,
909             @DisplayTypeEnum int expectedDisplay, int inputSource, int motionActionCode,
910             int buttonState, int pointerCount, int[] pointerIds, int[] toolTypes, float[] xData,
911             float[] yData, float[] pressureData, float[] sizeData, long downTimeNanos,
912             long timeStampNanos) {
913         ArgumentCaptor<MotionEvent> captor = ArgumentCaptor.forClass(MotionEvent.class);
914         reset(mInputListener);
915 
916         int[] int32Array = new int[5 + 2 * pointerCount];
917         float[] floatArray = new float[4 * pointerCount];
918         int32Array[0] = actualDisplay;
919         int32Array[1] = inputSource;
920         int32Array[2] = motionActionCode;
921         int32Array[3] = buttonState;
922         int32Array[4] = pointerCount;
923         for (int i = 0; i < pointerCount; i++) {
924             int32Array[5 + i] = pointerIds[i];
925             int32Array[5 + pointerCount + i] = toolTypes[i];
926             floatArray[i] = xData[i];
927             floatArray[pointerCount + i] = yData[i];
928             floatArray[2 * pointerCount + i] = pressureData[i];
929             floatArray[3 * pointerCount + i] = sizeData[i];
930         }
931 
932         mInputHalService.onHalEvents(
933                 List.of(mPropValueBuilder.build(VehicleProperty.HW_MOTION_INPUT,
934                         /* areaId = */ seat,
935                         /* timestamp= */ timeStampNanos,
936                         VehiclePropertyStatus.AVAILABLE,
937                         int32Array,
938                         floatArray,
939                         /* int64Values= */ new long[] {downTimeNanos},
940                         /* stringValue= */ "",
941                         /* byteValues= */ new byte[] {}
942                 )));
943         verify(mInputListener).onMotionEvent(captor.capture(), eq(expectedDisplay), eq(seat));
944         reset(mInputListener);
945         return captor.getValue();
946     }
947 
dispatchSingleEventWithIndents(int code, int indents, int actualDisplay, @DisplayTypeEnum int expectedDisplay)948     private KeyEvent dispatchSingleEventWithIndents(int code, int indents, int actualDisplay,
949             @DisplayTypeEnum int expectedDisplay) {
950         ArgumentCaptor<KeyEvent> captor = ArgumentCaptor.forClass(KeyEvent.class);
951         reset(mInputListener);
952         mInputHalService.onHalEvents(
953                 List.of(makeKeyPropValueWithIndents(code, indents, actualDisplay)));
954         verify(mInputListener, times(indents)).onKeyEvent(captor.capture(),
955                 eq(expectedDisplay));
956         reset(mInputListener);
957         return captor.getValue();
958     }
959 
makeKeyPropValue(Key action, int code, @DisplayTypeEnum int targetDisplayType)960     private HalPropValue makeKeyPropValue(Key action, int code,
961             @DisplayTypeEnum int targetDisplayType) {
962         int actionValue = (action == Key.DOWN
963                         ? VehicleHwKeyInputAction.ACTION_DOWN
964                         : VehicleHwKeyInputAction.ACTION_UP);
965         return mPropValueBuilder.build(VehicleProperty.HW_KEY_INPUT, /* areaId= */ 0,
966                 new int[]{actionValue, code, targetDisplayType});
967     }
968 
makeKeyPropValueWithIndents(int code, int indents, @DisplayTypeEnum int targetDisplayType)969     private HalPropValue makeKeyPropValueWithIndents(int code, int indents,
970             @DisplayTypeEnum int targetDisplayType) {
971         // Only Key.down can have indents.
972         return mPropValueBuilder.build(VehicleProperty.HW_KEY_INPUT, /* areaId= */ 0,
973                 new int[]{VehicleHwKeyInputAction.ACTION_DOWN, code, targetDisplayType, indents});
974     }
975 
makeRotaryPropValue(int rotaryInputType, int detents, long timestamp, int delayBetweenDetents, @DisplayTypeEnum int targetDisplayType)976     private HalPropValue makeRotaryPropValue(int rotaryInputType, int detents, long timestamp,
977             int delayBetweenDetents, @DisplayTypeEnum int targetDisplayType) {
978         ArrayList<Integer> int32Values = new ArrayList<>();
979         int32Values.add(rotaryInputType);
980         int32Values.add(detents);
981         int32Values.add(targetDisplayType);
982         for (int i = 0; i < Math.abs(detents) - 1; i++) {
983             int32Values.add(delayBetweenDetents);
984         }
985         return mPropValueBuilder.build(VehicleProperty.HW_ROTARY_INPUT, /* areaId= */ 0, timestamp,
986                 /*status=*/0, toIntArray(int32Values));
987     }
988 
makeCustomInputPropValue(int inputCode, @DisplayTypeEnum int targetDisplayType, int repeatCounter)989     private HalPropValue makeCustomInputPropValue(int inputCode,
990             @DisplayTypeEnum int targetDisplayType, int repeatCounter) {
991         return mPropValueBuilder.build(VehicleProperty.HW_CUSTOM_INPUT, /* areaId= */ 0,
992                 new int[]{inputCode, targetDisplayType, repeatCounter});
993     }
994 }
995