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 com.google.common.truth.Truth.assertThat;
19 
20 import static org.mockito.ArgumentMatchers.any;
21 import static org.mockito.ArgumentMatchers.anyInt;
22 import static org.mockito.ArgumentMatchers.eq;
23 import static org.mockito.Mockito.doAnswer;
24 import static org.mockito.Mockito.never;
25 import static org.mockito.Mockito.reset;
26 import static org.mockito.Mockito.times;
27 import static org.mockito.Mockito.verify;
28 import static org.mockito.Mockito.when;
29 
30 import android.hardware.automotive.vehicle.V2_0.VehicleHwKeyInputAction;
31 import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig;
32 import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
33 import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
34 import android.view.KeyEvent;
35 
36 import androidx.test.filters.RequiresDevice;
37 
38 import com.android.car.vehiclehal.test.VehiclePropConfigBuilder;
39 
40 import com.google.common.collect.ImmutableList;
41 import com.google.common.collect.ImmutableSet;
42 
43 import org.junit.After;
44 import org.junit.Before;
45 import org.junit.Test;
46 import org.junit.runner.RunWith;
47 import org.mockito.ArgumentCaptor;
48 import org.mockito.Mock;
49 import org.mockito.junit.MockitoJUnitRunner;
50 
51 import java.util.ArrayList;
52 import java.util.List;
53 import java.util.Set;
54 import java.util.function.LongSupplier;
55 
56 @RunWith(MockitoJUnitRunner.class)
57 public class InputHalServiceTest {
58     @Mock VehicleHal mVehicleHal;
59     @Mock InputHalService.InputListener mInputListener;
60     @Mock LongSupplier mUptimeSupplier;
61 
62     private static final VehiclePropConfig HW_KEY_INPUT_CONFIG =
63             VehiclePropConfigBuilder.newBuilder(VehicleProperty.HW_KEY_INPUT).build();
64     private static final VehiclePropConfig HW_ROTARY_INPUT_CONFIG =
65             VehiclePropConfigBuilder.newBuilder(VehicleProperty.HW_ROTARY_INPUT).build();
66     private static final int DISPLAY = 42;
67 
68     private enum Key { DOWN, UP }
69 
70     private InputHalService mInputHalService;
71 
72     @Before
setUp()73     public void setUp() {
74         when(mUptimeSupplier.getAsLong()).thenReturn(0L);
75         mInputHalService = new InputHalService(mVehicleHal, mUptimeSupplier);
76         mInputHalService.init();
77     }
78 
79     @After
tearDown()80     public void tearDown() {
81         mInputHalService.release();
82         mInputHalService = null;
83     }
84 
85     @Test
ignoresSetListener_beforeKeyInputSupported()86     public void ignoresSetListener_beforeKeyInputSupported() {
87         assertThat(mInputHalService.isKeyInputSupported()).isFalse();
88 
89         mInputHalService.setInputListener(mInputListener);
90 
91         mInputHalService.onHalEvents(
92                 ImmutableList.of(makeKeyPropValue(Key.DOWN, KeyEvent.KEYCODE_ENTER)));
93         verify(mInputListener, never()).onKeyEvent(any(), anyInt());
94     }
95 
96     @Test
takesKeyInputProperty()97     public void takesKeyInputProperty() {
98         Set<VehiclePropConfig> offeredProps = ImmutableSet.of(
99                 VehiclePropConfigBuilder.newBuilder(VehicleProperty.ABS_ACTIVE).build(),
100                 HW_KEY_INPUT_CONFIG,
101                 VehiclePropConfigBuilder.newBuilder(VehicleProperty.CURRENT_GEAR).build());
102 
103         mInputHalService.takeProperties(offeredProps);
104 
105         assertThat(mInputHalService.isKeyInputSupported()).isTrue();
106         assertThat(mInputHalService.isRotaryInputSupported()).isFalse();
107     }
108 
109     @Test
takesRotaryInputProperty()110     public void takesRotaryInputProperty() {
111         Set<VehiclePropConfig> offeredProps = ImmutableSet.of(
112                 VehiclePropConfigBuilder.newBuilder(VehicleProperty.ABS_ACTIVE).build(),
113                 HW_ROTARY_INPUT_CONFIG,
114                 VehiclePropConfigBuilder.newBuilder(VehicleProperty.CURRENT_GEAR).build());
115 
116         mInputHalService.takeProperties(offeredProps);
117 
118         assertThat(mInputHalService.isRotaryInputSupported()).isTrue();
119         assertThat(mInputHalService.isKeyInputSupported()).isFalse();
120     }
121 
122     @Test
takesKeyAndRotaryInputProperty()123     public void takesKeyAndRotaryInputProperty() {
124         Set<VehiclePropConfig> offeredProps = ImmutableSet.of(
125                 VehiclePropConfigBuilder.newBuilder(VehicleProperty.ABS_ACTIVE).build(),
126                 HW_KEY_INPUT_CONFIG,
127                 HW_ROTARY_INPUT_CONFIG,
128                 VehiclePropConfigBuilder.newBuilder(VehicleProperty.CURRENT_GEAR).build());
129 
130         mInputHalService.takeProperties(offeredProps);
131 
132         assertThat(mInputHalService.isKeyInputSupported()).isTrue();
133         assertThat(mInputHalService.isRotaryInputSupported()).isTrue();
134     }
135 
136     @Test
dispatchesInputEvent_single_toListener()137     public void dispatchesInputEvent_single_toListener() {
138         subscribeListener();
139 
140         KeyEvent event = dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_ENTER);
141         assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_DOWN);
142         assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER);
143     }
144 
145     @Test
dispatchesInputEvent_multiple_toListener()146     public void dispatchesInputEvent_multiple_toListener() {
147         subscribeListener();
148 
149         // KeyEvents get recycled, so we can't just use ArgumentCaptor#getAllValues here.
150         // We need to make a copy of the information we need at the time of the call.
151         List<KeyEvent> events = new ArrayList<>();
152         doAnswer(inv -> {
153             KeyEvent event = inv.getArgument(0);
154             events.add(event.copy());
155             return null;
156         }).when(mInputListener).onKeyEvent(any(), eq(DISPLAY));
157 
158         mInputHalService.onHalEvents(
159                 ImmutableList.of(
160                         makeKeyPropValue(Key.DOWN, KeyEvent.KEYCODE_ENTER),
161                         makeKeyPropValue(Key.DOWN, KeyEvent.KEYCODE_MENU)));
162 
163         assertThat(events.get(0).getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER);
164         assertThat(events.get(1).getKeyCode()).isEqualTo(KeyEvent.KEYCODE_MENU);
165 
166         events.forEach(KeyEvent::recycle);
167     }
168 
169     @Test
handlesRepeatedKeys()170     public void handlesRepeatedKeys() {
171         subscribeListener();
172 
173         KeyEvent event = dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_ENTER);
174         assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_DOWN);
175         assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER);
176         assertThat(event.getEventTime()).isEqualTo(0L);
177         assertThat(event.getDownTime()).isEqualTo(0L);
178         assertThat(event.getRepeatCount()).isEqualTo(0);
179 
180         when(mUptimeSupplier.getAsLong()).thenReturn(5L);
181         event = dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_ENTER);
182 
183         assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_DOWN);
184         assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER);
185         assertThat(event.getEventTime()).isEqualTo(5L);
186         assertThat(event.getDownTime()).isEqualTo(5L);
187         assertThat(event.getRepeatCount()).isEqualTo(1);
188 
189         when(mUptimeSupplier.getAsLong()).thenReturn(10L);
190         event = dispatchSingleEvent(Key.UP, KeyEvent.KEYCODE_ENTER);
191 
192         assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_UP);
193         assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER);
194         assertThat(event.getEventTime()).isEqualTo(10L);
195         assertThat(event.getDownTime()).isEqualTo(5L);
196         assertThat(event.getRepeatCount()).isEqualTo(0);
197 
198         when(mUptimeSupplier.getAsLong()).thenReturn(15L);
199         event = dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_ENTER);
200 
201         assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_DOWN);
202         assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER);
203         assertThat(event.getEventTime()).isEqualTo(15L);
204         assertThat(event.getDownTime()).isEqualTo(15L);
205         assertThat(event.getRepeatCount()).isEqualTo(0);
206     }
207 
208     /**
209      * Test for handling rotary knob event.
210      */
211     @RequiresDevice
212     @Test
handlesRepeatedKeyWithIndents()213     public void handlesRepeatedKeyWithIndents() {
214         subscribeListener();
215         KeyEvent event = dispatchSingleEventWithIndents(KeyEvent.KEYCODE_VOLUME_UP, 5);
216         assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_DOWN);
217         assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_VOLUME_UP);
218         assertThat(event.getEventTime()).isEqualTo(0L);
219         assertThat(event.getDownTime()).isEqualTo(0L);
220         assertThat(event.getRepeatCount()).isEqualTo(4);
221 
222         when(mUptimeSupplier.getAsLong()).thenReturn(5L);
223         event = dispatchSingleEventWithIndents(KeyEvent.KEYCODE_VOLUME_UP, 5);
224         assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_DOWN);
225         assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_VOLUME_UP);
226         assertThat(event.getEventTime()).isEqualTo(5L);
227         assertThat(event.getDownTime()).isEqualTo(5L);
228         assertThat(event.getRepeatCount()).isEqualTo(9);
229     }
230 
231     @Test
handlesKeyUp_withoutKeyDown()232     public void handlesKeyUp_withoutKeyDown() {
233         subscribeListener();
234 
235         when(mUptimeSupplier.getAsLong()).thenReturn(42L);
236         KeyEvent event = dispatchSingleEvent(Key.UP, KeyEvent.KEYCODE_ENTER);
237 
238         assertThat(event.getEventTime()).isEqualTo(42L);
239         assertThat(event.getDownTime()).isEqualTo(42L);
240         assertThat(event.getRepeatCount()).isEqualTo(0);
241     }
242 
243     @Test
separateKeyDownEvents_areIndependent()244     public void separateKeyDownEvents_areIndependent() {
245         subscribeListener();
246 
247         when(mUptimeSupplier.getAsLong()).thenReturn(27L);
248         dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_ENTER);
249 
250         when(mUptimeSupplier.getAsLong()).thenReturn(42L);
251         KeyEvent event = dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_MENU);
252 
253         assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_MENU);
254         assertThat(event.getDownTime()).isEqualTo(42L);
255         assertThat(event.getRepeatCount()).isEqualTo(0);
256     }
257 
258     @Test
dispatchesRotaryEvent_singleVolumeUp_toListener()259     public void dispatchesRotaryEvent_singleVolumeUp_toListener() {
260         // TODO(b/151225008) Update this
261         /*
262         subscribeListener();
263 
264         // KeyEvents get recycled, so we can't just use ArgumentCaptor#getAllValues here.
265         // We need to make a copy of the information we need at the time of the call.
266         List<KeyEvent> events = new ArrayList<>();
267         doAnswer(inv -> {
268             KeyEvent event = inv.getArgument(0);
269             events.add(event.copy());
270             return null;
271         }).when(mInputListener).onKeyEvent(any(), eq(DISPLAY));
272 
273         long timestampNanos = 12345678901L;
274         mInputHalService.onHalEvents(ImmutableList.of(
275                 makeRotaryPropValue(RotaryInputType.ROTARY_INPUT_TYPE_AUDIO_VOLUME, 1,
276                         timestampNanos, 0)));
277 
278         long timestampMillis = timestampNanos / 1000000;
279         KeyEvent downEvent = events.get(0);
280         assertThat(downEvent.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_VOLUME_UP);
281         assertThat(downEvent.getAction()).isEqualTo(KeyEvent.ACTION_DOWN);
282         assertThat(downEvent.getEventTime()).isEqualTo(timestampMillis);
283         KeyEvent upEvent = events.get(1);
284         assertThat(upEvent.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_VOLUME_UP);
285         assertThat(upEvent.getAction()).isEqualTo(KeyEvent.ACTION_UP);
286         assertThat(upEvent.getEventTime()).isEqualTo(timestampMillis);
287 
288         events.forEach(KeyEvent::recycle);*/
289     }
290 
291     @Test
dispatchesRotaryEvent_multipleNavigatePrevious_toListener()292     public void dispatchesRotaryEvent_multipleNavigatePrevious_toListener() {
293         // TODO(b/151225008) Update this
294         /*
295         subscribeListener();
296 
297         // KeyEvents get recycled, so we can't just use ArgumentCaptor#getAllValues here.
298         // We need to make a copy of the information we need at the time of the call.
299         List<KeyEvent> events = new ArrayList<>();
300         doAnswer(inv -> {
301             KeyEvent event = inv.getArgument(0);
302             events.add(event.copy());
303             return null;
304         }).when(mInputListener).onKeyEvent(any(), eq(DISPLAY));
305 
306         long timestampNanos = 12345678901L;
307         int deltaNanos = 876543210;
308         int numberOfDetents = 3;
309         mInputHalService.onHalEvents(ImmutableList.of(
310                 makeRotaryPropValue(RotaryInputType.ROTARY_INPUT_TYPE_SYSTEM_NAVIGATION,
311                         -numberOfDetents, timestampNanos, deltaNanos)));
312 
313         for (int i = 0; i < numberOfDetents; i++) {
314             long timestampMillis = (timestampNanos + i * (long) deltaNanos) / 1000000;
315             KeyEvent downEvent = events.get(i * 2);
316             assertThat(downEvent.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_NAVIGATE_PREVIOUS);
317             assertThat(downEvent.getAction()).isEqualTo(KeyEvent.ACTION_DOWN);
318             assertThat(downEvent.getEventTime()).isEqualTo(timestampMillis);
319             KeyEvent upEvent = events.get(i * 2 + 1);
320             assertThat(upEvent.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_NAVIGATE_PREVIOUS);
321             assertThat(upEvent.getAction()).isEqualTo(KeyEvent.ACTION_UP);
322             assertThat(upEvent.getEventTime()).isEqualTo(timestampMillis);
323         }
324 
325         events.forEach(KeyEvent::recycle);*/
326     }
327 
subscribeListener()328     private void subscribeListener() {
329         mInputHalService.takeProperties(ImmutableSet.of(HW_KEY_INPUT_CONFIG));
330         assertThat(mInputHalService.isKeyInputSupported()).isTrue();
331 
332         mInputHalService.setInputListener(mInputListener);
333         verify(mVehicleHal).subscribeProperty(mInputHalService, VehicleProperty.HW_KEY_INPUT);
334     }
335 
336 
makeKeyPropValue(Key action, int code)337     private VehiclePropValue makeKeyPropValue(Key action, int code) {
338         VehiclePropValue v = new VehiclePropValue();
339         v.prop = VehicleProperty.HW_KEY_INPUT;
340         v.value.int32Values.add(
341                 (action == Key.DOWN
342                         ? VehicleHwKeyInputAction.ACTION_DOWN
343                         : VehicleHwKeyInputAction.ACTION_UP));
344         v.value.int32Values.add(code);
345         v.value.int32Values.add(DISPLAY);
346         return v;
347     }
348 
dispatchSingleEvent(Key action, int code)349     private KeyEvent dispatchSingleEvent(Key action, int code) {
350         ArgumentCaptor<KeyEvent> captor = ArgumentCaptor.forClass(KeyEvent.class);
351         reset(mInputListener);
352         mInputHalService.onHalEvents(ImmutableList.of(makeKeyPropValue(action, code)));
353         verify(mInputListener).onKeyEvent(captor.capture(), eq(DISPLAY));
354         reset(mInputListener);
355         return captor.getValue();
356     }
357 
makeKeyPropValueWithIndents(int code, int indents)358     private VehiclePropValue makeKeyPropValueWithIndents(int code, int indents) {
359         VehiclePropValue v = new VehiclePropValue();
360         v.prop = VehicleProperty.HW_KEY_INPUT;
361         // Only Key.down can have indents.
362         v.value.int32Values.add(VehicleHwKeyInputAction.ACTION_DOWN);
363         v.value.int32Values.add(code);
364         v.value.int32Values.add(DISPLAY);
365         v.value.int32Values.add(indents);
366         return v;
367     }
368 
dispatchSingleEventWithIndents(int code, int indents)369     private KeyEvent dispatchSingleEventWithIndents(int code, int indents) {
370         ArgumentCaptor<KeyEvent> captor = ArgumentCaptor.forClass(KeyEvent.class);
371         reset(mInputListener);
372         mInputHalService.onHalEvents(
373                 ImmutableList.of(makeKeyPropValueWithIndents(code, indents)));
374         verify(mInputListener, times(indents)).onKeyEvent(captor.capture(), eq(DISPLAY));
375         reset(mInputListener);
376         return captor.getValue();
377     }
378 
makeRotaryPropValue(int rotaryInputType, int detents, long timestamp, int delayBetweenDetents)379     private VehiclePropValue makeRotaryPropValue(int rotaryInputType, int detents, long timestamp,
380             int delayBetweenDetents) {
381         VehiclePropValue v = new VehiclePropValue();
382         v.prop = VehicleProperty.HW_ROTARY_INPUT;
383         v.value.int32Values.add(rotaryInputType);
384         v.value.int32Values.add(detents);
385         v.value.int32Values.add(DISPLAY);
386         for (int i = 0; i < Math.abs(detents) - 1; i++) {
387             v.value.int32Values.add(delayBetweenDetents);
388         }
389         v.timestamp = timestamp;
390         return v;
391     }
392 }