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 25 import static org.mockito.ArgumentMatchers.any; 26 import static org.mockito.ArgumentMatchers.anyInt; 27 import static org.mockito.ArgumentMatchers.eq; 28 import static org.mockito.Mockito.doAnswer; 29 import static org.mockito.Mockito.never; 30 import static org.mockito.Mockito.reset; 31 import static org.mockito.Mockito.times; 32 import static org.mockito.Mockito.verify; 33 import static org.mockito.Mockito.when; 34 35 import android.car.CarOccupantZoneManager; 36 import android.car.input.CarInputManager; 37 import android.car.input.CustomInputEvent; 38 import android.car.input.RotaryEvent; 39 import android.hardware.automotive.vehicle.RotaryInputType; 40 import android.hardware.automotive.vehicle.VehicleDisplay; 41 import android.hardware.automotive.vehicle.VehicleHwKeyInputAction; 42 import android.hardware.automotive.vehicle.VehicleProperty; 43 import android.view.Display; 44 import android.view.KeyEvent; 45 46 import androidx.test.filters.RequiresDevice; 47 48 import com.android.car.hal.test.AidlVehiclePropConfigBuilder; 49 50 import org.junit.After; 51 import org.junit.Before; 52 import org.junit.Test; 53 import org.junit.runner.RunWith; 54 import org.mockito.ArgumentCaptor; 55 import org.mockito.Mock; 56 import org.mockito.junit.MockitoJUnitRunner; 57 58 import java.util.ArrayList; 59 import java.util.List; 60 import java.util.Set; 61 import java.util.function.LongSupplier; 62 63 @RunWith(MockitoJUnitRunner.class) 64 public class InputHalServiceTest { 65 @Mock VehicleHal mVehicleHal; 66 @Mock InputHalService.InputListener mInputListener; 67 @Mock LongSupplier mUptimeSupplier; 68 69 private static final HalPropConfig HW_KEY_INPUT_CONFIG = new AidlHalPropConfig( 70 AidlVehiclePropConfigBuilder.newBuilder(VehicleProperty.HW_KEY_INPUT).build()); 71 private static final HalPropConfig HW_ROTARY_INPUT_CONFIG = new AidlHalPropConfig( 72 AidlVehiclePropConfigBuilder.newBuilder(VehicleProperty.HW_ROTARY_INPUT).build()); 73 private static final HalPropConfig HW_CUSTOM_INPUT_CONFIG = new AidlHalPropConfig( 74 AidlVehiclePropConfigBuilder.newBuilder(VehicleProperty.HW_CUSTOM_INPUT).build()); 75 76 private final HalPropValueBuilder mPropValueBuilder = new HalPropValueBuilder(/*isAidl=*/true); 77 78 private enum Key {DOWN, UP} 79 80 private InputHalService mInputHalService; 81 82 @Before setUp()83 public void setUp() { 84 when(mUptimeSupplier.getAsLong()).thenReturn(0L); 85 mInputHalService = new InputHalService(mVehicleHal, mUptimeSupplier); 86 mInputHalService.init(); 87 } 88 89 @After tearDown()90 public void tearDown() { 91 mInputHalService.release(); 92 mInputHalService = null; 93 } 94 95 @Test ignoresSetListener_beforeKeyInputSupported()96 public void ignoresSetListener_beforeKeyInputSupported() { 97 assertThat(mInputHalService.isKeyInputSupported()).isFalse(); 98 99 mInputHalService.setInputListener(mInputListener); 100 101 int anyDisplay = VehicleDisplay.MAIN; 102 mInputHalService.onHalEvents(List.of(makeKeyPropValue(Key.DOWN, KeyEvent.KEYCODE_ENTER, 103 anyDisplay))); 104 verify(mInputListener, never()).onKeyEvent(any(), anyInt()); 105 } 106 107 @Test takesKeyInputProperty()108 public void takesKeyInputProperty() { 109 Set<HalPropConfig> offeredProps = Set.of(new AidlHalPropConfig( 110 AidlVehiclePropConfigBuilder.newBuilder(VehicleProperty.ABS_ACTIVE).build()), 111 HW_KEY_INPUT_CONFIG, 112 new AidlHalPropConfig(AidlVehiclePropConfigBuilder.newBuilder( 113 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<HalPropConfig> offeredProps = Set.of( 125 new AidlHalPropConfig(AidlVehiclePropConfigBuilder.newBuilder( 126 VehicleProperty.ABS_ACTIVE).build()), 127 HW_ROTARY_INPUT_CONFIG, 128 new AidlHalPropConfig(AidlVehiclePropConfigBuilder.newBuilder( 129 VehicleProperty.CURRENT_GEAR).build())); 130 131 mInputHalService.takeProperties(offeredProps); 132 133 assertThat(mInputHalService.isRotaryInputSupported()).isTrue(); 134 assertThat(mInputHalService.isKeyInputSupported()).isFalse(); 135 assertThat(mInputHalService.isCustomInputSupported()).isFalse(); 136 } 137 138 @Test takesCustomInputProperty()139 public void takesCustomInputProperty() { 140 Set<HalPropConfig> offeredProps = Set.of( 141 new AidlHalPropConfig(AidlVehiclePropConfigBuilder.newBuilder( 142 VehicleProperty.ABS_ACTIVE).build()), 143 HW_CUSTOM_INPUT_CONFIG, 144 new AidlHalPropConfig(AidlVehiclePropConfigBuilder.newBuilder( 145 VehicleProperty.CURRENT_GEAR).build())); 146 147 mInputHalService.takeProperties(offeredProps); 148 149 assertThat(mInputHalService.isRotaryInputSupported()).isFalse(); 150 assertThat(mInputHalService.isKeyInputSupported()).isFalse(); 151 assertThat(mInputHalService.isCustomInputSupported()).isTrue(); 152 } 153 154 @Test takesKeyAndRotaryAndCustomInputProperty()155 public void takesKeyAndRotaryAndCustomInputProperty() { 156 Set<HalPropConfig> offeredProps = Set.of( 157 new AidlHalPropConfig(AidlVehiclePropConfigBuilder.newBuilder( 158 VehicleProperty.ABS_ACTIVE).build()), 159 HW_KEY_INPUT_CONFIG, 160 HW_ROTARY_INPUT_CONFIG, 161 HW_CUSTOM_INPUT_CONFIG, 162 new AidlHalPropConfig(AidlVehiclePropConfigBuilder.newBuilder( 163 VehicleProperty.CURRENT_GEAR).build())); 164 165 mInputHalService.takeProperties(offeredProps); 166 167 assertThat(mInputHalService.isKeyInputSupported()).isTrue(); 168 assertThat(mInputHalService.isRotaryInputSupported()).isTrue(); 169 assertThat(mInputHalService.isCustomInputSupported()).isTrue(); 170 } 171 172 @Test dispatchesInputEvent_single_toListener_mainDisplay()173 public void dispatchesInputEvent_single_toListener_mainDisplay() { 174 subscribeListener(); 175 176 KeyEvent event = dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_ENTER, VehicleDisplay.MAIN, 177 CarOccupantZoneManager.DISPLAY_TYPE_MAIN); 178 assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_DOWN); 179 assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER); 180 } 181 182 @Test dispatchesInputEvent_single_toListener_clusterDisplay()183 public void dispatchesInputEvent_single_toListener_clusterDisplay() { 184 subscribeListener(); 185 186 KeyEvent event = dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_ENTER, 187 VehicleDisplay.INSTRUMENT_CLUSTER, 188 CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER); 189 assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_DOWN); 190 assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER); 191 } 192 193 @Test dispatchesInputEvent_multiple_toListener_mainDisplay()194 public void dispatchesInputEvent_multiple_toListener_mainDisplay() { 195 subscribeListener(); 196 197 // KeyEvents get recycled, so we can't just use ArgumentCaptor#getAllValues here. 198 // We need to make a copy of the information we need at the time of the call. 199 List<KeyEvent> events = new ArrayList<>(); 200 doAnswer(inv -> { 201 KeyEvent event = inv.getArgument(0); 202 events.add(event.copy()); 203 return null; 204 }).when(mInputListener).onKeyEvent(any(), eq(CarOccupantZoneManager.DISPLAY_TYPE_MAIN)); 205 206 mInputHalService.onHalEvents( 207 List.of( 208 makeKeyPropValue(Key.DOWN, KeyEvent.KEYCODE_ENTER, VehicleDisplay.MAIN), 209 makeKeyPropValue(Key.DOWN, KeyEvent.KEYCODE_MENU, VehicleDisplay.MAIN))); 210 211 assertThat(events.get(0).getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER); 212 assertThat(events.get(1).getKeyCode()).isEqualTo(KeyEvent.KEYCODE_MENU); 213 214 events.forEach(KeyEvent::recycle); 215 } 216 217 @Test dispatchesInputEvent_multiple_toListener_clusterDisplay()218 public void dispatchesInputEvent_multiple_toListener_clusterDisplay() { 219 subscribeListener(); 220 221 // KeyEvents get recycled, so we can't just use ArgumentCaptor#getAllValues here. 222 // We need to make a copy of the information we need at the time of the call. 223 List<KeyEvent> events = new ArrayList<>(); 224 doAnswer(inv -> { 225 KeyEvent event = inv.getArgument(0); 226 events.add(event.copy()); 227 return null; 228 }).when(mInputListener).onKeyEvent(any(), 229 eq(CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER)); 230 231 mInputHalService.onHalEvents( 232 List.of( 233 makeKeyPropValue(Key.DOWN, KeyEvent.KEYCODE_ENTER, 234 VehicleDisplay.INSTRUMENT_CLUSTER), 235 makeKeyPropValue(Key.DOWN, KeyEvent.KEYCODE_MENU, 236 VehicleDisplay.INSTRUMENT_CLUSTER))); 237 238 assertThat(events.get(0).getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER); 239 assertThat(events.get(1).getKeyCode()).isEqualTo(KeyEvent.KEYCODE_MENU); 240 241 events.forEach(KeyEvent::recycle); 242 } 243 244 @Test dispatchesInputEvent_invalidInputEvent()245 public void dispatchesInputEvent_invalidInputEvent() { 246 subscribeListener(); 247 HalPropValue v = mPropValueBuilder.build(VehicleProperty.HW_KEY_INPUT, /* areaId= */ 0); 248 // Missing action, code, display_type. 249 mInputHalService.onHalEvents(List.of(v)); 250 verify(mInputListener, never()).onKeyEvent(any(), anyInt()); 251 252 // Missing code, display_type. 253 v = mPropValueBuilder.build(VehicleProperty.HW_KEY_INPUT, /* areaId= */ 0, 254 VehicleHwKeyInputAction.ACTION_DOWN); 255 mInputHalService.onHalEvents(List.of(v)); 256 verify(mInputListener, never()).onKeyEvent(any(), anyInt()); 257 258 // Missing display_type. 259 v = mPropValueBuilder.build(VehicleProperty.HW_KEY_INPUT, /* areaId= */ 0, 260 new int[]{VehicleHwKeyInputAction.ACTION_DOWN, KeyEvent.KEYCODE_ENTER}); 261 mInputHalService.onHalEvents(List.of(v)); 262 verify(mInputListener, never()).onKeyEvent(any(), anyInt()); 263 } 264 265 @Test handlesRepeatedKeys_anyDisplay()266 public void handlesRepeatedKeys_anyDisplay() { 267 subscribeListener(); 268 269 KeyEvent event = dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_ENTER, VehicleDisplay.MAIN, 270 CarOccupantZoneManager.DISPLAY_TYPE_MAIN); 271 assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_DOWN); 272 assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER); 273 assertThat(event.getEventTime()).isEqualTo(0L); 274 assertThat(event.getDownTime()).isEqualTo(0L); 275 assertThat(event.getRepeatCount()).isEqualTo(0); 276 277 when(mUptimeSupplier.getAsLong()).thenReturn(5L); 278 event = dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_ENTER, VehicleDisplay.MAIN, 279 CarOccupantZoneManager.DISPLAY_TYPE_MAIN); 280 281 assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_DOWN); 282 assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER); 283 assertThat(event.getEventTime()).isEqualTo(5L); 284 assertThat(event.getDownTime()).isEqualTo(5L); 285 assertThat(event.getRepeatCount()).isEqualTo(1); 286 287 when(mUptimeSupplier.getAsLong()).thenReturn(10L); 288 event = dispatchSingleEvent(Key.UP, KeyEvent.KEYCODE_ENTER, VehicleDisplay.MAIN, 289 CarOccupantZoneManager.DISPLAY_TYPE_MAIN); 290 291 assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_UP); 292 assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER); 293 assertThat(event.getEventTime()).isEqualTo(10L); 294 assertThat(event.getDownTime()).isEqualTo(5L); 295 assertThat(event.getRepeatCount()).isEqualTo(0); 296 297 when(mUptimeSupplier.getAsLong()).thenReturn(15L); 298 event = dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_ENTER, VehicleDisplay.MAIN, 299 CarOccupantZoneManager.DISPLAY_TYPE_MAIN); 300 301 assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_DOWN); 302 assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER); 303 assertThat(event.getEventTime()).isEqualTo(15L); 304 assertThat(event.getDownTime()).isEqualTo(15L); 305 assertThat(event.getRepeatCount()).isEqualTo(0); 306 } 307 308 /** 309 * Test for handling rotary knob event. 310 */ 311 @RequiresDevice 312 @Test handlesRepeatedKeyWithIndents_anyDisplay()313 public void handlesRepeatedKeyWithIndents_anyDisplay() { 314 subscribeListener(); 315 KeyEvent event = dispatchSingleEventWithIndents(KeyEvent.KEYCODE_VOLUME_UP, 5, 316 VehicleDisplay.MAIN, CarOccupantZoneManager.DISPLAY_TYPE_MAIN); 317 assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_DOWN); 318 assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_VOLUME_UP); 319 assertThat(event.getEventTime()).isEqualTo(0L); 320 assertThat(event.getDownTime()).isEqualTo(0L); 321 assertThat(event.getRepeatCount()).isEqualTo(4); 322 323 when(mUptimeSupplier.getAsLong()).thenReturn(5L); 324 event = dispatchSingleEventWithIndents(KeyEvent.KEYCODE_VOLUME_UP, 5, 325 VehicleDisplay.MAIN, CarOccupantZoneManager.DISPLAY_TYPE_MAIN); 326 assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_DOWN); 327 assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_VOLUME_UP); 328 assertThat(event.getEventTime()).isEqualTo(5L); 329 assertThat(event.getDownTime()).isEqualTo(5L); 330 assertThat(event.getRepeatCount()).isEqualTo(9); 331 } 332 333 @Test handlesKeyUp_withoutKeyDown_mainDisplay()334 public void handlesKeyUp_withoutKeyDown_mainDisplay() { 335 subscribeListener(); 336 337 when(mUptimeSupplier.getAsLong()).thenReturn(42L); 338 KeyEvent event = dispatchSingleEvent(Key.UP, KeyEvent.KEYCODE_ENTER, VehicleDisplay.MAIN, 339 CarOccupantZoneManager.DISPLAY_TYPE_MAIN); 340 341 assertThat(event.getEventTime()).isEqualTo(42L); 342 assertThat(event.getDownTime()).isEqualTo(42L); 343 assertThat(event.getRepeatCount()).isEqualTo(0); 344 assertThat(event.getDisplayId()).isEqualTo(Display.DEFAULT_DISPLAY); 345 } 346 347 @Test handlesKeyUp_withoutKeyDown_clusterDisplay()348 public void handlesKeyUp_withoutKeyDown_clusterDisplay() { 349 subscribeListener(); 350 351 when(mUptimeSupplier.getAsLong()).thenReturn(42L); 352 KeyEvent event = dispatchSingleEvent(Key.UP, KeyEvent.KEYCODE_ENTER, 353 VehicleDisplay.INSTRUMENT_CLUSTER, 354 CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER); 355 356 assertThat(event.getEventTime()).isEqualTo(42L); 357 assertThat(event.getDownTime()).isEqualTo(42L); 358 assertThat(event.getRepeatCount()).isEqualTo(0); 359 // event.getDisplayId is not tested since it is assigned by CarInputService 360 } 361 362 @Test separateKeyDownEvents_areIndependent_mainDisplay()363 public void separateKeyDownEvents_areIndependent_mainDisplay() { 364 subscribeListener(); 365 366 when(mUptimeSupplier.getAsLong()).thenReturn(27L); 367 dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_ENTER, VehicleDisplay.MAIN, 368 CarOccupantZoneManager.DISPLAY_TYPE_MAIN); 369 370 when(mUptimeSupplier.getAsLong()).thenReturn(42L); 371 KeyEvent event = dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_MENU, VehicleDisplay.MAIN, 372 CarOccupantZoneManager.DISPLAY_TYPE_MAIN); 373 374 assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_MENU); 375 assertThat(event.getDownTime()).isEqualTo(42L); 376 assertThat(event.getRepeatCount()).isEqualTo(0); 377 } 378 379 @Test separateKeyDownEvents_areIndependent_clusterDisplay()380 public void separateKeyDownEvents_areIndependent_clusterDisplay() { 381 subscribeListener(); 382 383 when(mUptimeSupplier.getAsLong()).thenReturn(27L); 384 dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_ENTER, VehicleDisplay.INSTRUMENT_CLUSTER, 385 CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER); 386 387 when(mUptimeSupplier.getAsLong()).thenReturn(42L); 388 KeyEvent event = dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_MENU, VehicleDisplay.MAIN, 389 CarOccupantZoneManager.DISPLAY_TYPE_MAIN); 390 391 assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_MENU); 392 assertThat(event.getDownTime()).isEqualTo(42L); 393 assertThat(event.getRepeatCount()).isEqualTo(0); 394 // event.getDisplayid is not tested since it is assigned by CarInputService 395 } 396 397 @Test dispatchesRotaryEvent_singleVolumeUp_anyDisplay()398 public void dispatchesRotaryEvent_singleVolumeUp_anyDisplay() { 399 subscribeListener(); 400 401 // Arrange mInputListener to capture incoming RotaryEvent 402 List<RotaryEvent> events = new ArrayList<>(); 403 doAnswer(invocation -> { 404 RotaryEvent event = invocation.getArgument(0); 405 events.add(event); 406 return null; 407 }).when(mInputListener).onRotaryEvent(any(), eq(CarOccupantZoneManager.DISPLAY_TYPE_MAIN)); 408 409 // Arrange 410 long timestampNanos = 12_345_678_901L; 411 412 // Act 413 mInputHalService.onHalEvents(List.of( 414 makeRotaryPropValue(RotaryInputType.ROTARY_INPUT_TYPE_AUDIO_VOLUME, 1, 415 timestampNanos, 0, VehicleDisplay.MAIN))); 416 417 // Assert 418 419 // Expected Rotary event to have only one value for uptimeMillisForClicks since the input 420 // property was created with one detent only. This value will correspond to the event 421 // startup time. See CarServiceUtils#getUptimeToElapsedTimeDeltaInMillis for more detailed 422 // information on how this value is calculated. 423 assertThat(events).containsExactly(new RotaryEvent( 424 /* inputType= */ CarInputManager.INPUT_TYPE_ROTARY_VOLUME, 425 /* clockwise= */ true, 426 /* uptimeMillisForClicks= */ new long[]{12345L})); 427 } 428 429 @Test dispatchesRotaryEvent_multipleNavigatePrevious_anyDisplay()430 public void dispatchesRotaryEvent_multipleNavigatePrevious_anyDisplay() { 431 subscribeListener(); 432 433 // Arrange mInputListener to capture incoming RotaryEvent 434 List<RotaryEvent> events = new ArrayList<>(); 435 doAnswer(invocation -> { 436 RotaryEvent event = invocation.getArgument(0); 437 events.add(event); 438 return null; 439 }).when(mInputListener).onRotaryEvent(any(), eq(CarOccupantZoneManager.DISPLAY_TYPE_MAIN)); 440 441 // Arrange 442 long timestampNanos = 12_345_000_000L; 443 int deltaNanos = 2_000_000; 444 int numberOfDetents = 3; 445 446 // Act 447 mInputHalService.onHalEvents(List.of( 448 makeRotaryPropValue(RotaryInputType.ROTARY_INPUT_TYPE_SYSTEM_NAVIGATION, 449 -numberOfDetents, timestampNanos, deltaNanos, VehicleDisplay.MAIN))); 450 451 // Assert 452 453 // Expected Rotary event to have 3 values for uptimeMillisForClicks since the input 454 // property value was created with 3 detents. Each value in uptimeMillisForClicks 455 // represents the calculated deltas (in nanoseconds) between pairs of consecutive detents 456 // up times. See InputHalService#dispatchRotaryInput for more detailed information on how 457 // delta times are calculated. 458 assertThat(events).containsExactly(new RotaryEvent( 459 /* inputType= */CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION, 460 /* clockwise= */ false, 461 /* uptimeMillisForClicks= */ new long[]{12345L, 12347L, 12349L})); 462 } 463 464 @Test dispatchesRotaryEvent_invalidInputEvent()465 public void dispatchesRotaryEvent_invalidInputEvent() { 466 subscribeListener(); 467 HalPropValue v = mPropValueBuilder.build(VehicleProperty.HW_ROTARY_INPUT, /* areaId= */ 0); 468 469 // Missing rotaryInputType, detentCount, targetDisplayType. 470 mInputHalService.onHalEvents(List.of(v)); 471 verify(mInputListener, never()).onRotaryEvent(any(), anyInt()); 472 473 // Missing detentCount, targetDisplayType. 474 v = mPropValueBuilder.build(VehicleProperty.HW_ROTARY_INPUT, /* areaId= */ 0, 475 RotaryInputType.ROTARY_INPUT_TYPE_SYSTEM_NAVIGATION); 476 mInputHalService.onHalEvents(List.of(v)); 477 verify(mInputListener, never()).onRotaryEvent(any(), anyInt()); 478 479 // Missing targetDisplayType. 480 v = mPropValueBuilder.build(VehicleProperty.HW_ROTARY_INPUT, /* areaId= */ 0, 481 new int[]{RotaryInputType.ROTARY_INPUT_TYPE_SYSTEM_NAVIGATION, 1}); 482 mInputHalService.onHalEvents(List.of(v)); 483 verify(mInputListener, never()).onRotaryEvent(any(), anyInt()); 484 485 // Add targetDisplayType and set detentCount to 0. 486 v = mPropValueBuilder.build(VehicleProperty.HW_ROTARY_INPUT, /* areaId= */ 0, 487 new int[]{RotaryInputType.ROTARY_INPUT_TYPE_SYSTEM_NAVIGATION, 0, 488 VehicleDisplay.MAIN}); 489 mInputHalService.onHalEvents(List.of(v)); 490 verify(mInputListener, never()).onRotaryEvent(any(), anyInt()); 491 492 // Set detentCount to 1. 493 // Add additional unnecessary arguments so that the array size does not match detentCount. 494 v = mPropValueBuilder.build(VehicleProperty.HW_ROTARY_INPUT, /* areaId= */ 1, 495 new int[]{RotaryInputType.ROTARY_INPUT_TYPE_SYSTEM_NAVIGATION, 1, 496 VehicleDisplay.MAIN, 0}); 497 mInputHalService.onHalEvents(List.of(v)); 498 verify(mInputListener, never()).onRotaryEvent(any(), anyInt()); 499 500 // Set invalid detentCount. 501 v = mPropValueBuilder.build(VehicleProperty.HW_ROTARY_INPUT, /* areaId= */ 1, 502 new int[]{RotaryInputType.ROTARY_INPUT_TYPE_SYSTEM_NAVIGATION, Integer.MAX_VALUE, 503 VehicleDisplay.MAIN, 0}); 504 mInputHalService.onHalEvents(List.of(v)); 505 verify(mInputListener, never()).onRotaryEvent(any(), anyInt()); 506 507 // Set invalid detentCount. 508 v = mPropValueBuilder.build(VehicleProperty.HW_ROTARY_INPUT, /* areaId= */ 1, 509 new int[]{RotaryInputType.ROTARY_INPUT_TYPE_SYSTEM_NAVIGATION, Integer.MIN_VALUE, 510 VehicleDisplay.MAIN, 0}); 511 mInputHalService.onHalEvents(List.of(v)); 512 verify(mInputListener, never()).onRotaryEvent(any(), anyInt()); 513 } 514 515 @Test dispatchesCustomInputEvent_mainDisplay()516 public void dispatchesCustomInputEvent_mainDisplay() { 517 // Arrange mInputListener to capture incoming CustomInputEvent 518 subscribeListener(); 519 520 List<CustomInputEvent> events = new ArrayList<>(); 521 doAnswer(invocation -> { 522 CustomInputEvent event = invocation.getArgument(0); 523 events.add(event); 524 return null; 525 }).when(mInputListener).onCustomInputEvent(any()); 526 527 // Arrange 528 int repeatCounter = 1; 529 HalPropValue customInputPropValue = makeCustomInputPropValue( 530 CUSTOM_EVENT_F1, VehicleDisplay.MAIN, repeatCounter); 531 532 // Act 533 mInputHalService.onHalEvents(List.of(customInputPropValue)); 534 535 // Assert 536 assertThat(events).containsExactly(new CustomInputEvent( 537 CustomInputEvent.INPUT_CODE_F1, CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 538 repeatCounter)); 539 } 540 541 @Test dispatchesCustomInputEvent_clusterDisplay()542 public void dispatchesCustomInputEvent_clusterDisplay() { 543 // Arrange mInputListener to capture incoming CustomInputEvent 544 subscribeListener(); 545 546 List<CustomInputEvent> events = new ArrayList<>(); 547 doAnswer(invocation -> { 548 CustomInputEvent event = invocation.getArgument(0); 549 events.add(event); 550 return null; 551 }).when(mInputListener).onCustomInputEvent(any()); 552 553 // Arrange 554 int repeatCounter = 1; 555 HalPropValue customInputPropValue = makeCustomInputPropValue( 556 CUSTOM_EVENT_F1, VehicleDisplay.INSTRUMENT_CLUSTER, repeatCounter); 557 558 // Act 559 mInputHalService.onHalEvents(List.of(customInputPropValue)); 560 561 // Assert 562 assertThat(events).containsExactly(new CustomInputEvent( 563 CustomInputEvent.INPUT_CODE_F1, 564 CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER, 565 repeatCounter)); 566 } 567 568 @Test dispatchesCustomInputEvent_InvalidEvent()569 public void dispatchesCustomInputEvent_InvalidEvent() { 570 // Arrange mInputListener to capture incoming CustomInputEvent 571 subscribeListener(); 572 573 HalPropValue v = mPropValueBuilder.build(VehicleProperty.HW_CUSTOM_INPUT, /* areaId= */ 0); 574 575 // Missing inputCode, targetDisplayType, repeatCounter. 576 mInputHalService.onHalEvents(List.of(v)); 577 verify(mInputListener, never()).onCustomInputEvent(any()); 578 579 // Missing targetDisplayType, repeatCounter. 580 v = mPropValueBuilder.build(VehicleProperty.HW_CUSTOM_INPUT, /* areaId= */ 0, 581 CustomInputEvent.INPUT_CODE_F1); 582 mInputHalService.onHalEvents(List.of(v)); 583 verify(mInputListener, never()).onCustomInputEvent(any()); 584 585 // Missing repeatCounter. 586 v = mPropValueBuilder.build(VehicleProperty.HW_CUSTOM_INPUT, /* areaId= */ 0, 587 new int[]{CustomInputEvent.INPUT_CODE_F1, 588 CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER}); 589 mInputHalService.onHalEvents(List.of(v)); 590 591 verify(mInputListener, never()).onCustomInputEvent(any()); 592 } 593 594 @Test dispatchesCustomInputEvent_acceptInputCodeHigherThanF10()595 public void dispatchesCustomInputEvent_acceptInputCodeHigherThanF10() { 596 // Custom Input Events may accept input code values outside the 597 // CUSTOM_EVENT_F1 to F10 range. 598 int someInputCodeValueHigherThanF10 = 1000; 599 600 // Arrange mInputListener to capture incoming CustomInputEvent 601 subscribeListener(); 602 603 List<CustomInputEvent> events = new ArrayList<>(); 604 doAnswer(invocation -> { 605 CustomInputEvent event = invocation.getArgument(0); 606 events.add(event); 607 return null; 608 }).when(mInputListener).onCustomInputEvent(any()); 609 610 // Arrange 611 int repeatCounter = 1; 612 HalPropValue customInputPropValue = makeCustomInputPropValue( 613 someInputCodeValueHigherThanF10, VehicleDisplay.MAIN, repeatCounter); 614 615 // Act 616 mInputHalService.onHalEvents(List.of(customInputPropValue)); 617 618 // Assert 619 assertThat(events).containsExactly(new CustomInputEvent( 620 someInputCodeValueHigherThanF10, CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 621 repeatCounter)); 622 } 623 subscribeListener()624 private void subscribeListener() { 625 mInputHalService.takeProperties(Set.of(HW_KEY_INPUT_CONFIG)); 626 assertThat(mInputHalService.isKeyInputSupported()).isTrue(); 627 628 mInputHalService.setInputListener(mInputListener); 629 verify(mVehicleHal).subscribeProperty(mInputHalService, VehicleProperty.HW_KEY_INPUT); 630 } 631 dispatchSingleEvent(Key action, int code, int actualDisplay, @DisplayTypeEnum int expectedDisplay)632 private KeyEvent dispatchSingleEvent(Key action, int code, int actualDisplay, 633 @DisplayTypeEnum int expectedDisplay) { 634 ArgumentCaptor<KeyEvent> captor = ArgumentCaptor.forClass(KeyEvent.class); 635 reset(mInputListener); 636 mInputHalService.onHalEvents( 637 List.of(makeKeyPropValue(action, code, actualDisplay))); 638 verify(mInputListener).onKeyEvent(captor.capture(), eq(expectedDisplay)); 639 reset(mInputListener); 640 return captor.getValue(); 641 } 642 dispatchSingleEventWithIndents(int code, int indents, int actualDisplay, @DisplayTypeEnum int expectedDisplay)643 private KeyEvent dispatchSingleEventWithIndents(int code, int indents, int actualDisplay, 644 @DisplayTypeEnum int expectedDisplay) { 645 ArgumentCaptor<KeyEvent> captor = ArgumentCaptor.forClass(KeyEvent.class); 646 reset(mInputListener); 647 mInputHalService.onHalEvents( 648 List.of(makeKeyPropValueWithIndents(code, indents, actualDisplay))); 649 verify(mInputListener, times(indents)).onKeyEvent(captor.capture(), 650 eq(expectedDisplay)); 651 reset(mInputListener); 652 return captor.getValue(); 653 } 654 makeKeyPropValue(Key action, int code, @DisplayTypeEnum int targetDisplayType)655 private HalPropValue makeKeyPropValue(Key action, int code, 656 @DisplayTypeEnum int targetDisplayType) { 657 int actionValue = (action == Key.DOWN 658 ? VehicleHwKeyInputAction.ACTION_DOWN 659 : VehicleHwKeyInputAction.ACTION_UP); 660 return mPropValueBuilder.build(VehicleProperty.HW_KEY_INPUT, /* areaId= */ 0, 661 new int[]{actionValue, code, targetDisplayType}); 662 } 663 makeKeyPropValueWithIndents(int code, int indents, @DisplayTypeEnum int targetDisplayType)664 private HalPropValue makeKeyPropValueWithIndents(int code, int indents, 665 @DisplayTypeEnum int targetDisplayType) { 666 // Only Key.down can have indents. 667 return mPropValueBuilder.build(VehicleProperty.HW_KEY_INPUT, /* areaId= */ 0, 668 new int[]{VehicleHwKeyInputAction.ACTION_DOWN, code, targetDisplayType, indents}); 669 } 670 makeRotaryPropValue(int rotaryInputType, int detents, long timestamp, int delayBetweenDetents, @DisplayTypeEnum int targetDisplayType)671 private HalPropValue makeRotaryPropValue(int rotaryInputType, int detents, long timestamp, 672 int delayBetweenDetents, @DisplayTypeEnum int targetDisplayType) { 673 ArrayList<Integer> int32Values = new ArrayList<>(); 674 int32Values.add(rotaryInputType); 675 int32Values.add(detents); 676 int32Values.add(targetDisplayType); 677 for (int i = 0; i < Math.abs(detents) - 1; i++) { 678 int32Values.add(delayBetweenDetents); 679 } 680 return mPropValueBuilder.build(VehicleProperty.HW_ROTARY_INPUT, /* areaId= */ 0, timestamp, 681 /*status=*/0, toIntArray(int32Values)); 682 } 683 makeCustomInputPropValue(int inputCode, @DisplayTypeEnum int targetDisplayType, int repeatCounter)684 private HalPropValue makeCustomInputPropValue(int inputCode, 685 @DisplayTypeEnum int targetDisplayType, int repeatCounter) { 686 return mPropValueBuilder.build(VehicleProperty.HW_CUSTOM_INPUT, /* areaId= */ 0, 687 new int[]{inputCode, targetDisplayType, repeatCounter}); 688 } 689 } 690