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