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.verify;
27 import static org.mockito.Mockito.when;
28 
29 import android.hardware.automotive.vehicle.V2_0.VehicleHwKeyInputAction;
30 import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig;
31 import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
32 import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
33 import android.view.KeyEvent;
34 
35 import androidx.test.runner.AndroidJUnit4;
36 
37 import com.android.car.vehiclehal.test.VehiclePropConfigBuilder;
38 
39 import com.google.common.collect.ImmutableList;
40 import com.google.common.collect.ImmutableSet;
41 
42 import org.junit.After;
43 import org.junit.Before;
44 import org.junit.Rule;
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.MockitoJUnit;
50 import org.mockito.junit.MockitoRule;
51 
52 import java.util.ArrayList;
53 import java.util.Collection;
54 import java.util.List;
55 import java.util.Set;
56 import java.util.function.LongSupplier;
57 
58 @RunWith(AndroidJUnit4.class)
59 public class InputHalServiceTest {
60     @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
61 
62     @Mock VehicleHal mVehicleHal;
63     @Mock InputHalService.InputListener mInputListener;
64     @Mock LongSupplier mUptimeSupplier;
65 
66     private static final VehiclePropConfig HW_KEY_INPUT_CONFIG =
67             VehiclePropConfigBuilder.newBuilder(VehicleProperty.HW_KEY_INPUT).build();
68     private static final int DISPLAY = 42;
69 
70     private enum Key { DOWN, UP }
71 
72     private InputHalService mInputHalService;
73 
74     @Before
setUp()75     public void setUp() {
76         when(mUptimeSupplier.getAsLong()).thenReturn(0L);
77         mInputHalService = new InputHalService(mVehicleHal, mUptimeSupplier);
78         mInputHalService.init();
79     }
80 
81     @After
tearDown()82     public void tearDown() {
83         mInputHalService.release();
84         mInputHalService = null;
85     }
86 
87     @Test
ignoresSetListener_beforeKeyInputSupported()88     public void ignoresSetListener_beforeKeyInputSupported() {
89         assertThat(mInputHalService.isKeyInputSupported()).isFalse();
90 
91         mInputHalService.setInputListener(mInputListener);
92 
93         mInputHalService.handleHalEvents(
94                 ImmutableList.of(makeKeyPropValue(Key.DOWN, KeyEvent.KEYCODE_ENTER)));
95         verify(mInputListener, never()).onKeyEvent(any(), anyInt());
96     }
97 
98     @Test
takesKeyInputProperty()99     public void takesKeyInputProperty() {
100         Set<VehiclePropConfig> offeredProps = ImmutableSet.of(
101                 VehiclePropConfigBuilder.newBuilder(VehicleProperty.ABS_ACTIVE).build(),
102                 HW_KEY_INPUT_CONFIG,
103                 VehiclePropConfigBuilder.newBuilder(VehicleProperty.CURRENT_GEAR).build());
104 
105         Collection<VehiclePropConfig> takenProps =
106                 mInputHalService.takeSupportedProperties(offeredProps);
107 
108         assertThat(takenProps).containsExactly(HW_KEY_INPUT_CONFIG);
109         assertThat(mInputHalService.isKeyInputSupported()).isTrue();
110     }
111 
112     @Test
dispatchesInputEvent_single_toListener()113     public void dispatchesInputEvent_single_toListener() {
114         subscribeListener();
115 
116         KeyEvent event = dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_ENTER);
117         assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_DOWN);
118         assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER);
119     }
120 
121     @Test
dispatchesInputEvent_multiple_toListener()122     public void dispatchesInputEvent_multiple_toListener() {
123         subscribeListener();
124 
125         // KeyEvents get recycled, so we can't just use ArgumentCaptor#getAllValues here.
126         // We need to make a copy of the information we need at the time of the call.
127         List<KeyEvent> events = new ArrayList<>();
128         doAnswer(inv -> {
129             KeyEvent event = inv.getArgument(0);
130             events.add(event.copy());
131             return null;
132         }).when(mInputListener).onKeyEvent(any(), eq(DISPLAY));
133 
134         mInputHalService.handleHalEvents(
135                 ImmutableList.of(
136                         makeKeyPropValue(Key.DOWN, KeyEvent.KEYCODE_ENTER),
137                         makeKeyPropValue(Key.DOWN, KeyEvent.KEYCODE_MENU)));
138 
139         assertThat(events.get(0).getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER);
140         assertThat(events.get(1).getKeyCode()).isEqualTo(KeyEvent.KEYCODE_MENU);
141 
142         events.forEach(KeyEvent::recycle);
143     }
144 
145     @Test
handlesRepeatedKeys()146     public void handlesRepeatedKeys() {
147         subscribeListener();
148 
149         KeyEvent event = dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_ENTER);
150         assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_DOWN);
151         assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER);
152         assertThat(event.getEventTime()).isEqualTo(0L);
153         assertThat(event.getDownTime()).isEqualTo(0L);
154         assertThat(event.getRepeatCount()).isEqualTo(0);
155 
156         when(mUptimeSupplier.getAsLong()).thenReturn(5L);
157         event = dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_ENTER);
158 
159         assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_DOWN);
160         assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER);
161         assertThat(event.getEventTime()).isEqualTo(5L);
162         assertThat(event.getDownTime()).isEqualTo(5L);
163         assertThat(event.getRepeatCount()).isEqualTo(1);
164 
165         when(mUptimeSupplier.getAsLong()).thenReturn(10L);
166         event = dispatchSingleEvent(Key.UP, KeyEvent.KEYCODE_ENTER);
167 
168         assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_UP);
169         assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER);
170         assertThat(event.getEventTime()).isEqualTo(10L);
171         assertThat(event.getDownTime()).isEqualTo(5L);
172         assertThat(event.getRepeatCount()).isEqualTo(0);
173 
174         when(mUptimeSupplier.getAsLong()).thenReturn(15L);
175         event = dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_ENTER);
176 
177         assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_DOWN);
178         assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER);
179         assertThat(event.getEventTime()).isEqualTo(15L);
180         assertThat(event.getDownTime()).isEqualTo(15L);
181         assertThat(event.getRepeatCount()).isEqualTo(0);
182     }
183 
184     /**
185      * Test for handling rotary knob event.
186      */
187     @Test
handlesRepeatedKeyWithIndents()188     public void handlesRepeatedKeyWithIndents() {
189         subscribeListener();
190         KeyEvent event = dispatchSingleEventWithIndents(KeyEvent.KEYCODE_VOLUME_UP, 5);
191         assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_DOWN);
192         assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_VOLUME_UP);
193         assertThat(event.getEventTime()).isEqualTo(0L);
194         assertThat(event.getDownTime()).isEqualTo(0L);
195         assertThat(event.getRepeatCount()).isEqualTo(4);
196 
197         when(mUptimeSupplier.getAsLong()).thenReturn(5L);
198         event = dispatchSingleEventWithIndents(KeyEvent.KEYCODE_VOLUME_UP, 5);
199         assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_DOWN);
200         assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_VOLUME_UP);
201         assertThat(event.getEventTime()).isEqualTo(5L);
202         assertThat(event.getDownTime()).isEqualTo(5L);
203         assertThat(event.getRepeatCount()).isEqualTo(9);
204     }
205 
206     @Test
handlesKeyUp_withoutKeyDown()207     public void handlesKeyUp_withoutKeyDown() {
208         subscribeListener();
209 
210         when(mUptimeSupplier.getAsLong()).thenReturn(42L);
211         KeyEvent event = dispatchSingleEvent(Key.UP, KeyEvent.KEYCODE_ENTER);
212 
213         assertThat(event.getEventTime()).isEqualTo(42L);
214         assertThat(event.getDownTime()).isEqualTo(42L);
215         assertThat(event.getRepeatCount()).isEqualTo(0);
216     }
217 
218     @Test
separateKeyDownEvents_areIndependent()219     public void separateKeyDownEvents_areIndependent() {
220         subscribeListener();
221 
222         when(mUptimeSupplier.getAsLong()).thenReturn(27L);
223         dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_ENTER);
224 
225         when(mUptimeSupplier.getAsLong()).thenReturn(42L);
226         KeyEvent event = dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_MENU);
227 
228         assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_MENU);
229         assertThat(event.getDownTime()).isEqualTo(42L);
230         assertThat(event.getRepeatCount()).isEqualTo(0);
231     }
232 
subscribeListener()233     private void subscribeListener() {
234         mInputHalService.takeSupportedProperties(ImmutableSet.of(HW_KEY_INPUT_CONFIG));
235         assertThat(mInputHalService.isKeyInputSupported()).isTrue();
236 
237         mInputHalService.setInputListener(mInputListener);
238         verify(mVehicleHal).subscribeProperty(mInputHalService, VehicleProperty.HW_KEY_INPUT);
239     }
240 
241 
makeKeyPropValue(Key action, int code)242     private VehiclePropValue makeKeyPropValue(Key action, int code) {
243         VehiclePropValue v = new VehiclePropValue();
244         v.prop = VehicleProperty.HW_KEY_INPUT;
245         v.value.int32Values.add(
246                 (action == Key.DOWN
247                         ? VehicleHwKeyInputAction.ACTION_DOWN
248                         : VehicleHwKeyInputAction.ACTION_UP));
249         v.value.int32Values.add(code);
250         v.value.int32Values.add(DISPLAY);
251         return v;
252     }
253 
dispatchSingleEvent(Key action, int code)254     private KeyEvent dispatchSingleEvent(Key action, int code) {
255         ArgumentCaptor<KeyEvent> captor = ArgumentCaptor.forClass(KeyEvent.class);
256         reset(mInputListener);
257         mInputHalService.handleHalEvents(ImmutableList.of(makeKeyPropValue(action, code)));
258         verify(mInputListener).onKeyEvent(captor.capture(), eq(DISPLAY));
259         reset(mInputListener);
260         return captor.getValue();
261     }
262 
makeKeyPropValueWithIndents(int code, int indents)263     private VehiclePropValue makeKeyPropValueWithIndents(int code, int indents) {
264         VehiclePropValue v = new VehiclePropValue();
265         v.prop = VehicleProperty.HW_KEY_INPUT;
266         // Only Key.down can have indents.
267         v.value.int32Values.add(VehicleHwKeyInputAction.ACTION_DOWN);
268         v.value.int32Values.add(code);
269         v.value.int32Values.add(DISPLAY);
270         v.value.int32Values.add(indents);
271         return v;
272     }
273 
dispatchSingleEventWithIndents(int code, int indents)274     private KeyEvent dispatchSingleEventWithIndents(int code, int indents) {
275         ArgumentCaptor<KeyEvent> captor = ArgumentCaptor.forClass(KeyEvent.class);
276         reset(mInputListener);
277         mInputHalService.handleHalEvents(
278                 ImmutableList.of(makeKeyPropValueWithIndents(code, indents)));
279         verify(mInputListener).onKeyEvent(captor.capture(), eq(DISPLAY));
280         reset(mInputListener);
281         return captor.getValue();
282     }
283 }