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 }