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 import static com.google.common.truth.Truth.assertWithMessage; 25 26 import static org.mockito.ArgumentMatchers.any; 27 import static org.mockito.ArgumentMatchers.anyInt; 28 import static org.mockito.ArgumentMatchers.eq; 29 import static org.mockito.Mockito.atLeastOnce; 30 import static org.mockito.Mockito.doAnswer; 31 import static org.mockito.Mockito.never; 32 import static org.mockito.Mockito.reset; 33 import static org.mockito.Mockito.times; 34 import static org.mockito.Mockito.verify; 35 import static org.mockito.Mockito.when; 36 37 import static java.util.concurrent.TimeUnit.SECONDS; 38 39 import android.car.CarOccupantZoneManager; 40 import android.car.input.CarInputManager; 41 import android.car.input.CustomInputEvent; 42 import android.car.input.RotaryEvent; 43 import android.hardware.automotive.vehicle.RotaryInputType; 44 import android.hardware.automotive.vehicle.VehicleDisplay; 45 import android.hardware.automotive.vehicle.VehicleHwKeyInputAction; 46 import android.hardware.automotive.vehicle.VehicleHwMotionButtonStateFlag; 47 import android.hardware.automotive.vehicle.VehicleHwMotionInputAction; 48 import android.hardware.automotive.vehicle.VehicleHwMotionInputSource; 49 import android.hardware.automotive.vehicle.VehicleHwMotionToolType; 50 import android.hardware.automotive.vehicle.VehicleProperty; 51 import android.hardware.automotive.vehicle.VehiclePropertyStatus; 52 import android.view.InputDevice; 53 import android.view.KeyEvent; 54 import android.view.MotionEvent; 55 56 import androidx.test.filters.RequiresDevice; 57 58 import com.android.car.hal.test.AidlVehiclePropConfigBuilder; 59 60 import org.junit.After; 61 import org.junit.Before; 62 import org.junit.Test; 63 import org.junit.runner.RunWith; 64 import org.mockito.ArgumentCaptor; 65 import org.mockito.Mock; 66 import org.mockito.junit.MockitoJUnitRunner; 67 68 import java.io.PrintWriter; 69 import java.util.ArrayList; 70 import java.util.List; 71 import java.util.Set; 72 import java.util.function.LongSupplier; 73 74 @RunWith(MockitoJUnitRunner.class) 75 public class InputHalServiceTest { 76 @Mock VehicleHal mVehicleHal; 77 @Mock InputHalService.InputListener mInputListener; 78 @Mock LongSupplier mUptimeSupplier; 79 @Mock PrintWriter mMockPrintWriter; 80 81 private static final HalPropConfig HW_KEY_INPUT_CONFIG = new AidlHalPropConfig( 82 AidlVehiclePropConfigBuilder.newBuilder(VehicleProperty.HW_KEY_INPUT).build()); 83 private static final HalPropConfig HW_ROTARY_INPUT_CONFIG = new AidlHalPropConfig( 84 AidlVehiclePropConfigBuilder.newBuilder(VehicleProperty.HW_ROTARY_INPUT).build()); 85 private static final HalPropConfig HW_CUSTOM_INPUT_CONFIG = new AidlHalPropConfig( 86 AidlVehiclePropConfigBuilder.newBuilder(VehicleProperty.HW_CUSTOM_INPUT).build()); 87 private static final HalPropConfig HW_MOTION_INPUT = new AidlHalPropConfig( 88 AidlVehiclePropConfigBuilder.newBuilder(VehicleProperty.HW_MOTION_INPUT).build()); 89 private static final HalPropConfig HW_KEY_INPUT_V2 = new AidlHalPropConfig( 90 AidlVehiclePropConfigBuilder.newBuilder(VehicleProperty.HW_KEY_INPUT_V2).build()); 91 92 93 private final HalPropValueBuilder mPropValueBuilder = new HalPropValueBuilder(/*isAidl=*/true); 94 95 private enum Key {DOWN, UP} 96 97 private InputHalService mInputHalService; 98 99 @Before setUp()100 public void setUp() { 101 when(mUptimeSupplier.getAsLong()).thenReturn(0L); 102 mInputHalService = new InputHalService(mVehicleHal, mUptimeSupplier); 103 mInputHalService.init(); 104 } 105 106 @After tearDown()107 public void tearDown() { 108 mInputHalService.release(); 109 mInputHalService = null; 110 } 111 112 @Test ignoresSetListener_beforeKeyInputSupported()113 public void ignoresSetListener_beforeKeyInputSupported() { 114 assertThat(mInputHalService.isKeyInputSupported()).isFalse(); 115 116 mInputHalService.setInputListener(mInputListener); 117 118 int anyDisplay = VehicleDisplay.MAIN; 119 mInputHalService.onHalEvents(List.of(makeKeyPropValue(Key.DOWN, KeyEvent.KEYCODE_ENTER, 120 anyDisplay))); 121 verify(mInputListener, never()).onKeyEvent(any(), anyInt()); 122 } 123 124 @Test takesKeyInputProperty()125 public void takesKeyInputProperty() { 126 Set<HalPropConfig> offeredProps = Set.of(new AidlHalPropConfig( 127 AidlVehiclePropConfigBuilder.newBuilder(VehicleProperty.ABS_ACTIVE).build()), 128 HW_KEY_INPUT_CONFIG, 129 new AidlHalPropConfig(AidlVehiclePropConfigBuilder.newBuilder( 130 VehicleProperty.CURRENT_GEAR).build())); 131 132 mInputHalService.takeProperties(offeredProps); 133 134 assertThat(mInputHalService.isKeyInputSupported()).isTrue(); 135 assertThat(mInputHalService.isRotaryInputSupported()).isFalse(); 136 assertThat(mInputHalService.isCustomInputSupported()).isFalse(); 137 } 138 139 @Test takesRotaryInputProperty()140 public void takesRotaryInputProperty() { 141 Set<HalPropConfig> offeredProps = Set.of( 142 new AidlHalPropConfig(AidlVehiclePropConfigBuilder.newBuilder( 143 VehicleProperty.ABS_ACTIVE).build()), 144 HW_ROTARY_INPUT_CONFIG, 145 new AidlHalPropConfig(AidlVehiclePropConfigBuilder.newBuilder( 146 VehicleProperty.CURRENT_GEAR).build())); 147 148 mInputHalService.takeProperties(offeredProps); 149 150 assertThat(mInputHalService.isRotaryInputSupported()).isTrue(); 151 assertThat(mInputHalService.isKeyInputSupported()).isFalse(); 152 assertThat(mInputHalService.isCustomInputSupported()).isFalse(); 153 } 154 155 @Test takesCustomInputProperty()156 public void takesCustomInputProperty() { 157 Set<HalPropConfig> offeredProps = Set.of( 158 new AidlHalPropConfig(AidlVehiclePropConfigBuilder.newBuilder( 159 VehicleProperty.ABS_ACTIVE).build()), 160 HW_CUSTOM_INPUT_CONFIG, 161 new AidlHalPropConfig(AidlVehiclePropConfigBuilder.newBuilder( 162 VehicleProperty.CURRENT_GEAR).build())); 163 164 mInputHalService.takeProperties(offeredProps); 165 166 assertThat(mInputHalService.isRotaryInputSupported()).isFalse(); 167 assertThat(mInputHalService.isKeyInputSupported()).isFalse(); 168 assertThat(mInputHalService.isCustomInputSupported()).isTrue(); 169 assertThat(mInputHalService.isMotionInputSupported()).isFalse(); 170 assertThat(mInputHalService.isKeyInputV2Supported()).isFalse(); 171 } 172 173 @Test takesKeyAndRotaryAndCustomInputProperty()174 public void takesKeyAndRotaryAndCustomInputProperty() { 175 Set<HalPropConfig> offeredProps = Set.of( 176 new AidlHalPropConfig(AidlVehiclePropConfigBuilder.newBuilder( 177 VehicleProperty.ABS_ACTIVE).build()), 178 HW_KEY_INPUT_CONFIG, 179 HW_ROTARY_INPUT_CONFIG, 180 HW_CUSTOM_INPUT_CONFIG, 181 HW_MOTION_INPUT, 182 HW_KEY_INPUT_V2, 183 new AidlHalPropConfig(AidlVehiclePropConfigBuilder.newBuilder( 184 VehicleProperty.CURRENT_GEAR).build())); 185 186 mInputHalService.takeProperties(offeredProps); 187 188 assertThat(mInputHalService.isKeyInputSupported()).isTrue(); 189 assertThat(mInputHalService.isRotaryInputSupported()).isTrue(); 190 assertThat(mInputHalService.isCustomInputSupported()).isTrue(); 191 assertThat(mInputHalService.isKeyInputV2Supported()).isTrue(); 192 assertThat(mInputHalService.isMotionInputSupported()).isTrue(); 193 } 194 195 @Test dispatchesInputEvent_single_toListener_mainDisplay()196 public void dispatchesInputEvent_single_toListener_mainDisplay() { 197 subscribeListener(); 198 199 KeyEvent event = dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_ENTER, VehicleDisplay.MAIN, 200 CarOccupantZoneManager.DISPLAY_TYPE_MAIN); 201 assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_DOWN); 202 assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER); 203 } 204 205 @Test dispatchesInputEvent_single_toListener_clusterDisplay()206 public void dispatchesInputEvent_single_toListener_clusterDisplay() { 207 subscribeListener(); 208 209 KeyEvent event = dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_ENTER, 210 VehicleDisplay.INSTRUMENT_CLUSTER, 211 CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER); 212 assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_DOWN); 213 assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER); 214 } 215 216 @Test dispatchesInputEvent_multiple_toListener_mainDisplay()217 public void dispatchesInputEvent_multiple_toListener_mainDisplay() { 218 subscribeListener(); 219 220 // KeyEvents get recycled, so we can't just use ArgumentCaptor#getAllValues here. 221 // We need to make a copy of the information we need at the time of the call. 222 List<KeyEvent> events = new ArrayList<>(); 223 doAnswer(inv -> { 224 KeyEvent event = inv.getArgument(0); 225 events.add(event.copy()); 226 return null; 227 }).when(mInputListener).onKeyEvent(any(), eq(CarOccupantZoneManager.DISPLAY_TYPE_MAIN)); 228 229 mInputHalService.onHalEvents( 230 List.of( 231 makeKeyPropValue(Key.DOWN, KeyEvent.KEYCODE_ENTER, VehicleDisplay.MAIN), 232 makeKeyPropValue(Key.DOWN, KeyEvent.KEYCODE_MENU, VehicleDisplay.MAIN))); 233 234 assertThat(events.get(0).getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER); 235 assertThat(events.get(1).getKeyCode()).isEqualTo(KeyEvent.KEYCODE_MENU); 236 237 events.forEach(KeyEvent::recycle); 238 } 239 240 @Test dispatchesInputEvent_multiple_toListener_clusterDisplay()241 public void dispatchesInputEvent_multiple_toListener_clusterDisplay() { 242 subscribeListener(); 243 244 // KeyEvents get recycled, so we can't just use ArgumentCaptor#getAllValues here. 245 // We need to make a copy of the information we need at the time of the call. 246 List<KeyEvent> events = new ArrayList<>(); 247 doAnswer(inv -> { 248 KeyEvent event = inv.getArgument(0); 249 events.add(event.copy()); 250 return null; 251 }).when(mInputListener).onKeyEvent(any(), 252 eq(CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER)); 253 254 mInputHalService.onHalEvents( 255 List.of( 256 makeKeyPropValue(Key.DOWN, KeyEvent.KEYCODE_ENTER, 257 VehicleDisplay.INSTRUMENT_CLUSTER), 258 makeKeyPropValue(Key.DOWN, KeyEvent.KEYCODE_MENU, 259 VehicleDisplay.INSTRUMENT_CLUSTER))); 260 261 assertThat(events.get(0).getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER); 262 assertThat(events.get(1).getKeyCode()).isEqualTo(KeyEvent.KEYCODE_MENU); 263 264 events.forEach(KeyEvent::recycle); 265 } 266 267 @Test dispatchesInputEvent_invalidInputEvent()268 public void dispatchesInputEvent_invalidInputEvent() { 269 subscribeListener(); 270 HalPropValue v = mPropValueBuilder.build(VehicleProperty.HW_KEY_INPUT, /* areaId= */ 0); 271 // Missing action, code, display_type. 272 mInputHalService.onHalEvents(List.of(v)); 273 verify(mInputListener, never()).onKeyEvent(any(), anyInt()); 274 275 // Missing code, display_type. 276 v = mPropValueBuilder.build(VehicleProperty.HW_KEY_INPUT, /* areaId= */ 0, 277 VehicleHwKeyInputAction.ACTION_DOWN); 278 mInputHalService.onHalEvents(List.of(v)); 279 verify(mInputListener, never()).onKeyEvent(any(), anyInt()); 280 281 // Missing display_type. 282 v = mPropValueBuilder.build(VehicleProperty.HW_KEY_INPUT, /* areaId= */ 0, 283 new int[]{VehicleHwKeyInputAction.ACTION_DOWN, KeyEvent.KEYCODE_ENTER}); 284 mInputHalService.onHalEvents(List.of(v)); 285 verify(mInputListener, never()).onKeyEvent(any(), anyInt()); 286 } 287 288 @Test handlesRepeatedKeys_anyDisplay()289 public void handlesRepeatedKeys_anyDisplay() { 290 subscribeListener(); 291 292 KeyEvent event = dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_ENTER, VehicleDisplay.MAIN, 293 CarOccupantZoneManager.DISPLAY_TYPE_MAIN); 294 assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_DOWN); 295 assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER); 296 assertThat(event.getEventTime()).isEqualTo(0L); 297 assertThat(event.getDownTime()).isEqualTo(0L); 298 assertThat(event.getRepeatCount()).isEqualTo(0); 299 300 when(mUptimeSupplier.getAsLong()).thenReturn(5L); 301 event = dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_ENTER, VehicleDisplay.MAIN, 302 CarOccupantZoneManager.DISPLAY_TYPE_MAIN); 303 304 assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_DOWN); 305 assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER); 306 assertThat(event.getEventTime()).isEqualTo(5L); 307 assertThat(event.getDownTime()).isEqualTo(5L); 308 assertThat(event.getRepeatCount()).isEqualTo(1); 309 310 when(mUptimeSupplier.getAsLong()).thenReturn(10L); 311 event = dispatchSingleEvent(Key.UP, KeyEvent.KEYCODE_ENTER, VehicleDisplay.MAIN, 312 CarOccupantZoneManager.DISPLAY_TYPE_MAIN); 313 314 assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_UP); 315 assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER); 316 assertThat(event.getEventTime()).isEqualTo(10L); 317 assertThat(event.getDownTime()).isEqualTo(5L); 318 assertThat(event.getRepeatCount()).isEqualTo(0); 319 320 when(mUptimeSupplier.getAsLong()).thenReturn(15L); 321 event = dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_ENTER, VehicleDisplay.MAIN, 322 CarOccupantZoneManager.DISPLAY_TYPE_MAIN); 323 324 assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_DOWN); 325 assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_ENTER); 326 assertThat(event.getEventTime()).isEqualTo(15L); 327 assertThat(event.getDownTime()).isEqualTo(15L); 328 assertThat(event.getRepeatCount()).isEqualTo(0); 329 } 330 331 /** 332 * Test for handling rotary knob event. 333 */ 334 @RequiresDevice 335 @Test handlesRepeatedKeyWithIndents_anyDisplay()336 public void handlesRepeatedKeyWithIndents_anyDisplay() { 337 subscribeListener(); 338 KeyEvent event = dispatchSingleEventWithIndents(KeyEvent.KEYCODE_VOLUME_UP, 5, 339 VehicleDisplay.MAIN, CarOccupantZoneManager.DISPLAY_TYPE_MAIN); 340 assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_DOWN); 341 assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_VOLUME_UP); 342 assertThat(event.getEventTime()).isEqualTo(0L); 343 assertThat(event.getDownTime()).isEqualTo(0L); 344 assertThat(event.getRepeatCount()).isEqualTo(4); 345 346 when(mUptimeSupplier.getAsLong()).thenReturn(5L); 347 event = dispatchSingleEventWithIndents(KeyEvent.KEYCODE_VOLUME_UP, 5, 348 VehicleDisplay.MAIN, CarOccupantZoneManager.DISPLAY_TYPE_MAIN); 349 assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_DOWN); 350 assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_VOLUME_UP); 351 assertThat(event.getEventTime()).isEqualTo(5L); 352 assertThat(event.getDownTime()).isEqualTo(5L); 353 assertThat(event.getRepeatCount()).isEqualTo(9); 354 } 355 356 @Test handlesKeyUp_withoutKeyDown_mainDisplay()357 public void handlesKeyUp_withoutKeyDown_mainDisplay() { 358 subscribeListener(); 359 360 when(mUptimeSupplier.getAsLong()).thenReturn(42L); 361 KeyEvent event = dispatchSingleEvent(Key.UP, KeyEvent.KEYCODE_ENTER, VehicleDisplay.MAIN, 362 CarOccupantZoneManager.DISPLAY_TYPE_MAIN); 363 364 assertThat(event.getEventTime()).isEqualTo(42L); 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 handlesKeyUp_withoutKeyDown_clusterDisplay()371 public void handlesKeyUp_withoutKeyDown_clusterDisplay() { 372 subscribeListener(); 373 374 when(mUptimeSupplier.getAsLong()).thenReturn(42L); 375 KeyEvent event = dispatchSingleEvent(Key.UP, KeyEvent.KEYCODE_ENTER, 376 VehicleDisplay.INSTRUMENT_CLUSTER, 377 CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER); 378 379 assertThat(event.getEventTime()).isEqualTo(42L); 380 assertThat(event.getDownTime()).isEqualTo(42L); 381 assertThat(event.getRepeatCount()).isEqualTo(0); 382 // event.getDisplayId is not tested since it is assigned by CarInputService 383 } 384 385 @Test separateKeyDownEvents_areIndependent_mainDisplay()386 public void separateKeyDownEvents_areIndependent_mainDisplay() { 387 subscribeListener(); 388 389 when(mUptimeSupplier.getAsLong()).thenReturn(27L); 390 dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_ENTER, VehicleDisplay.MAIN, 391 CarOccupantZoneManager.DISPLAY_TYPE_MAIN); 392 393 when(mUptimeSupplier.getAsLong()).thenReturn(42L); 394 KeyEvent event = dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_MENU, VehicleDisplay.MAIN, 395 CarOccupantZoneManager.DISPLAY_TYPE_MAIN); 396 397 assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_MENU); 398 assertThat(event.getDownTime()).isEqualTo(42L); 399 assertThat(event.getRepeatCount()).isEqualTo(0); 400 } 401 402 @Test separateKeyDownEvents_areIndependent_clusterDisplay()403 public void separateKeyDownEvents_areIndependent_clusterDisplay() { 404 subscribeListener(); 405 406 when(mUptimeSupplier.getAsLong()).thenReturn(27L); 407 dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_ENTER, VehicleDisplay.INSTRUMENT_CLUSTER, 408 CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER); 409 410 when(mUptimeSupplier.getAsLong()).thenReturn(42L); 411 KeyEvent event = dispatchSingleEvent(Key.DOWN, KeyEvent.KEYCODE_MENU, VehicleDisplay.MAIN, 412 CarOccupantZoneManager.DISPLAY_TYPE_MAIN); 413 414 assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_MENU); 415 assertThat(event.getDownTime()).isEqualTo(42L); 416 assertThat(event.getRepeatCount()).isEqualTo(0); 417 // event.getDisplayid is not tested since it is assigned by CarInputService 418 } 419 420 @Test dispatchesRotaryEvent_singleVolumeUp_anyDisplay()421 public void dispatchesRotaryEvent_singleVolumeUp_anyDisplay() { 422 subscribeListener(); 423 424 // Arrange mInputListener to capture incoming RotaryEvent 425 List<RotaryEvent> events = new ArrayList<>(); 426 doAnswer(invocation -> { 427 RotaryEvent event = invocation.getArgument(0); 428 events.add(event); 429 return null; 430 }).when(mInputListener).onRotaryEvent(any(), eq(CarOccupantZoneManager.DISPLAY_TYPE_MAIN)); 431 432 // Arrange 433 long timestampNanos = 12_345_678_901L; 434 435 // Act 436 mInputHalService.onHalEvents(List.of( 437 makeRotaryPropValue(RotaryInputType.ROTARY_INPUT_TYPE_AUDIO_VOLUME, 1, 438 timestampNanos, 0, VehicleDisplay.MAIN))); 439 440 // Assert 441 442 // Expected Rotary event to have only one value for uptimeMillisForClicks since the input 443 // property was created with one detent only. This value will correspond to the event 444 // startup time. See CarServiceUtils#getUptimeToElapsedTimeDeltaInMillis for more detailed 445 // information on how this value is calculated. 446 assertThat(events).containsExactly(new RotaryEvent( 447 /* inputType= */ CarInputManager.INPUT_TYPE_ROTARY_VOLUME, 448 /* clockwise= */ true, 449 /* uptimeMillisForClicks= */ new long[]{12345L})); 450 } 451 452 @Test dispatchesRotaryEvent_multipleNavigatePrevious_anyDisplay()453 public void dispatchesRotaryEvent_multipleNavigatePrevious_anyDisplay() { 454 subscribeListener(); 455 456 // Arrange mInputListener to capture incoming RotaryEvent 457 List<RotaryEvent> events = new ArrayList<>(); 458 doAnswer(invocation -> { 459 RotaryEvent event = invocation.getArgument(0); 460 events.add(event); 461 return null; 462 }).when(mInputListener).onRotaryEvent(any(), eq(CarOccupantZoneManager.DISPLAY_TYPE_MAIN)); 463 464 // Arrange 465 long timestampNanos = 12_345_000_000L; 466 int deltaNanos = 2_000_000; 467 int numberOfDetents = 3; 468 469 // Act 470 mInputHalService.onHalEvents(List.of( 471 makeRotaryPropValue(RotaryInputType.ROTARY_INPUT_TYPE_SYSTEM_NAVIGATION, 472 -numberOfDetents, timestampNanos, deltaNanos, VehicleDisplay.MAIN))); 473 474 // Assert 475 476 // Expected Rotary event to have 3 values for uptimeMillisForClicks since the input 477 // property value was created with 3 detents. Each value in uptimeMillisForClicks 478 // represents the calculated deltas (in nanoseconds) between pairs of consecutive detents 479 // up times. See InputHalService#dispatchRotaryInput for more detailed information on how 480 // delta times are calculated. 481 assertThat(events).containsExactly(new RotaryEvent( 482 /* inputType= */CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION, 483 /* clockwise= */ false, 484 /* uptimeMillisForClicks= */ new long[]{12345L, 12347L, 12349L})); 485 } 486 487 @Test dispatchesRotaryEvent_invalidInputEvent()488 public void dispatchesRotaryEvent_invalidInputEvent() { 489 subscribeListener(); 490 HalPropValue v = mPropValueBuilder.build(VehicleProperty.HW_ROTARY_INPUT, /* areaId= */ 0); 491 492 // Missing rotaryInputType, detentCount, targetDisplayType. 493 mInputHalService.onHalEvents(List.of(v)); 494 verify(mInputListener, never()).onRotaryEvent(any(), anyInt()); 495 496 // Missing detentCount, targetDisplayType. 497 v = mPropValueBuilder.build(VehicleProperty.HW_ROTARY_INPUT, /* areaId= */ 0, 498 RotaryInputType.ROTARY_INPUT_TYPE_SYSTEM_NAVIGATION); 499 mInputHalService.onHalEvents(List.of(v)); 500 verify(mInputListener, never()).onRotaryEvent(any(), anyInt()); 501 502 // Missing targetDisplayType. 503 v = mPropValueBuilder.build(VehicleProperty.HW_ROTARY_INPUT, /* areaId= */ 0, 504 new int[]{RotaryInputType.ROTARY_INPUT_TYPE_SYSTEM_NAVIGATION, 1}); 505 mInputHalService.onHalEvents(List.of(v)); 506 verify(mInputListener, never()).onRotaryEvent(any(), anyInt()); 507 508 // Add targetDisplayType and set detentCount to 0. 509 v = mPropValueBuilder.build(VehicleProperty.HW_ROTARY_INPUT, /* areaId= */ 0, 510 new int[]{RotaryInputType.ROTARY_INPUT_TYPE_SYSTEM_NAVIGATION, 0, 511 VehicleDisplay.MAIN}); 512 mInputHalService.onHalEvents(List.of(v)); 513 verify(mInputListener, never()).onRotaryEvent(any(), anyInt()); 514 515 // Set detentCount to 1. 516 // Add additional unnecessary arguments so that the array size does not match detentCount. 517 v = mPropValueBuilder.build(VehicleProperty.HW_ROTARY_INPUT, /* areaId= */ 1, 518 new int[]{RotaryInputType.ROTARY_INPUT_TYPE_SYSTEM_NAVIGATION, 1, 519 VehicleDisplay.MAIN, 0}); 520 mInputHalService.onHalEvents(List.of(v)); 521 verify(mInputListener, never()).onRotaryEvent(any(), anyInt()); 522 523 // Set invalid detentCount. 524 v = mPropValueBuilder.build(VehicleProperty.HW_ROTARY_INPUT, /* areaId= */ 1, 525 new int[]{RotaryInputType.ROTARY_INPUT_TYPE_SYSTEM_NAVIGATION, Integer.MAX_VALUE, 526 VehicleDisplay.MAIN, 0}); 527 mInputHalService.onHalEvents(List.of(v)); 528 verify(mInputListener, never()).onRotaryEvent(any(), anyInt()); 529 530 // Set invalid detentCount. 531 v = mPropValueBuilder.build(VehicleProperty.HW_ROTARY_INPUT, /* areaId= */ 1, 532 new int[]{RotaryInputType.ROTARY_INPUT_TYPE_SYSTEM_NAVIGATION, Integer.MIN_VALUE, 533 VehicleDisplay.MAIN, 0}); 534 mInputHalService.onHalEvents(List.of(v)); 535 verify(mInputListener, never()).onRotaryEvent(any(), anyInt()); 536 } 537 538 @Test dispatchesCustomInputEvent_mainDisplay()539 public void dispatchesCustomInputEvent_mainDisplay() { 540 // Arrange mInputListener to capture incoming CustomInputEvent 541 subscribeListener(); 542 543 List<CustomInputEvent> events = new ArrayList<>(); 544 doAnswer(invocation -> { 545 CustomInputEvent event = invocation.getArgument(0); 546 events.add(event); 547 return null; 548 }).when(mInputListener).onCustomInputEvent(any()); 549 550 // Arrange 551 int repeatCounter = 1; 552 HalPropValue customInputPropValue = makeCustomInputPropValue( 553 CUSTOM_EVENT_F1, VehicleDisplay.MAIN, repeatCounter); 554 555 // Act 556 mInputHalService.onHalEvents(List.of(customInputPropValue)); 557 558 // Assert 559 assertThat(events).containsExactly(new CustomInputEvent( 560 CustomInputEvent.INPUT_CODE_F1, CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 561 repeatCounter)); 562 } 563 564 @Test dispatchesCustomInputEvent_clusterDisplay()565 public void dispatchesCustomInputEvent_clusterDisplay() { 566 // Arrange mInputListener to capture incoming CustomInputEvent 567 subscribeListener(); 568 569 List<CustomInputEvent> events = new ArrayList<>(); 570 doAnswer(invocation -> { 571 CustomInputEvent event = invocation.getArgument(0); 572 events.add(event); 573 return null; 574 }).when(mInputListener).onCustomInputEvent(any()); 575 576 // Arrange 577 int repeatCounter = 1; 578 HalPropValue customInputPropValue = makeCustomInputPropValue( 579 CUSTOM_EVENT_F1, VehicleDisplay.INSTRUMENT_CLUSTER, repeatCounter); 580 581 // Act 582 mInputHalService.onHalEvents(List.of(customInputPropValue)); 583 584 // Assert 585 assertThat(events).containsExactly(new CustomInputEvent( 586 CustomInputEvent.INPUT_CODE_F1, 587 CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER, 588 repeatCounter)); 589 } 590 591 @Test dispatchesCustomInputEvent_InvalidEvent()592 public void dispatchesCustomInputEvent_InvalidEvent() { 593 // Arrange mInputListener to capture incoming CustomInputEvent 594 subscribeListener(); 595 596 HalPropValue v = mPropValueBuilder.build(VehicleProperty.HW_CUSTOM_INPUT, /* areaId= */ 0); 597 598 // Missing inputCode, targetDisplayType, repeatCounter. 599 mInputHalService.onHalEvents(List.of(v)); 600 verify(mInputListener, never()).onCustomInputEvent(any()); 601 602 // Missing targetDisplayType, repeatCounter. 603 v = mPropValueBuilder.build(VehicleProperty.HW_CUSTOM_INPUT, /* areaId= */ 0, 604 CustomInputEvent.INPUT_CODE_F1); 605 mInputHalService.onHalEvents(List.of(v)); 606 verify(mInputListener, never()).onCustomInputEvent(any()); 607 608 // Missing repeatCounter. 609 v = mPropValueBuilder.build(VehicleProperty.HW_CUSTOM_INPUT, /* areaId= */ 0, 610 new int[]{CustomInputEvent.INPUT_CODE_F1, 611 CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER}); 612 mInputHalService.onHalEvents(List.of(v)); 613 614 verify(mInputListener, never()).onCustomInputEvent(any()); 615 } 616 617 @Test dispatchesCustomInputEvent_acceptInputCodeHigherThanF10()618 public void dispatchesCustomInputEvent_acceptInputCodeHigherThanF10() { 619 // Custom Input Events may accept input code values outside the 620 // CUSTOM_EVENT_F1 to F10 range. 621 int someInputCodeValueHigherThanF10 = 1000; 622 623 // Arrange mInputListener to capture incoming CustomInputEvent 624 subscribeListener(); 625 626 List<CustomInputEvent> events = new ArrayList<>(); 627 doAnswer(invocation -> { 628 CustomInputEvent event = invocation.getArgument(0); 629 events.add(event); 630 return null; 631 }).when(mInputListener).onCustomInputEvent(any()); 632 633 // Arrange 634 int repeatCounter = 1; 635 HalPropValue customInputPropValue = makeCustomInputPropValue( 636 someInputCodeValueHigherThanF10, VehicleDisplay.MAIN, repeatCounter); 637 638 // Act 639 mInputHalService.onHalEvents(List.of(customInputPropValue)); 640 641 // Assert 642 assertThat(events).containsExactly(new CustomInputEvent( 643 someInputCodeValueHigherThanF10, CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 644 repeatCounter)); 645 } 646 647 @Test dispatchesKeyInputEvent_perSeat_anyDisplay()648 public void dispatchesKeyInputEvent_perSeat_anyDisplay() { 649 // Arrange 650 subscribeListener(); 651 652 // Act 653 KeyEvent event = dispatchSinglePerSeatEvent( 654 /* seat= */ 1, 655 VehicleDisplay.MAIN, 656 CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 657 KeyEvent.KEYCODE_HOME, 658 Key.UP, 659 /* repeatCount= */ 1, 660 /* downTime= */ SECONDS.toNanos(7), 661 /* timeStampNanos= */ SECONDS.toNanos(8) 662 ); 663 664 // Assert 665 assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_HOME); 666 assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_UP); 667 assertThat(event.getRepeatCount()).isEqualTo(1); 668 assertThat(event.getDownTime()).isEqualTo(SECONDS.toMillis(7)); 669 assertThat(event.getEventTime()).isEqualTo(SECONDS.toMillis(8)); 670 } 671 672 @Test dispatchesKeyInputEvent_withSameEventTimeAndDownTime_perSeat_whenKeyDown()673 public void dispatchesKeyInputEvent_withSameEventTimeAndDownTime_perSeat_whenKeyDown() { 674 // Arrange 675 subscribeListener(); 676 677 // Act 678 KeyEvent event = dispatchSinglePerSeatEvent( 679 /* seat= */ 1, 680 VehicleDisplay.MAIN, 681 CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 682 KeyEvent.KEYCODE_HOME, 683 Key.DOWN, 684 /* repeatCount= */ 1, 685 /* downTime= */ SECONDS.toNanos(7), 686 /* timeStampNanos= */ SECONDS.toNanos(8) 687 ); 688 689 // Assert 690 assertThat(event.getKeyCode()).isEqualTo(KeyEvent.KEYCODE_HOME); 691 assertThat(event.getAction()).isEqualTo(KeyEvent.ACTION_DOWN); 692 assertThat(event.getRepeatCount()).isEqualTo(1); 693 assertThat(event.getDownTime()).isEqualTo(SECONDS.toMillis(7)); 694 assertThat(event.getEventTime()).isEqualTo(SECONDS.toMillis(7)); 695 } 696 697 @Test dispatchesMotionInputEvent_perSeat_anyDisplay()698 public void dispatchesMotionInputEvent_perSeat_anyDisplay() { 699 // Arrange 700 subscribeListener(); 701 702 // Act 703 MotionEvent event = dispatchSinglePerSeatMotionEvent( 704 /* seat= */ 1, 705 VehicleDisplay.MAIN, 706 CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 707 VehicleHwMotionInputSource.SOURCE_TOUCHSCREEN, 708 VehicleHwMotionInputAction.ACTION_DOWN, 709 VehicleHwMotionButtonStateFlag.BUTTON_PRIMARY, 710 /* pointerCount= */ 2, 711 /* pointerIds= */ new int[] {10, 20}, 712 /* toolTypes= */ new int[] {VehicleHwMotionToolType.TOOL_TYPE_FINGER, 713 VehicleHwMotionToolType.TOOL_TYPE_MOUSE}, 714 /* xData= */ new float[] {100, 200}, 715 /* yData= */ new float[] {300, 400}, 716 /* pressureData= */ new float[] {1, 2}, 717 /* sizeData= */ new float[] {3, 4}, 718 /* downTimeNanos= */ SECONDS.toNanos(7), 719 /* timeStampNanos= */ SECONDS.toNanos(8) 720 ); 721 722 // Assert 723 assertThat(event.getSource()).isEqualTo(InputDevice.SOURCE_TOUCHSCREEN); 724 assertThat(event.getAction()).isEqualTo(MotionEvent.ACTION_DOWN); 725 assertThat(event.getButtonState()).isEqualTo(MotionEvent.BUTTON_PRIMARY); 726 assertThat(event.getPointerCount()).isEqualTo(2); 727 assertThat(event.getPointerId(0)).isEqualTo(10); 728 assertThat(event.getPointerId(1)).isEqualTo(20); 729 assertThat(event.getToolType(0)).isEqualTo(MotionEvent.TOOL_TYPE_FINGER); 730 assertThat(event.getToolType(1)).isEqualTo(MotionEvent.TOOL_TYPE_MOUSE); 731 assertThat(event.getX(0)).isEqualTo(100); 732 assertThat(event.getX(1)).isEqualTo(200); 733 assertThat(event.getY(0)).isEqualTo(300); 734 assertThat(event.getY(1)).isEqualTo(400); 735 assertThat(event.getPressure(0)).isEqualTo(1); 736 assertThat(event.getPressure(1)).isEqualTo(2); 737 assertThat(event.getSize(0)).isEqualTo(3); 738 assertThat(event.getSize(1)).isEqualTo(4); 739 assertThat(event.getDownTime()).isEqualTo(SECONDS.toMillis(7)); 740 assertThat(event.getEventTime()).isEqualTo(SECONDS.toMillis(8)); 741 } 742 743 @Test dump_withNoLastKeyEvent_printsEmpty()744 public void dump_withNoLastKeyEvent_printsEmpty() { 745 // Arrange 746 ArgumentCaptor<String> stringArgumentCaptor = ArgumentCaptor.forClass(String.class); 747 748 // Act 749 mInputHalService.dump(mMockPrintWriter); 750 751 // Assert 752 verify(mMockPrintWriter, atLeastOnce()).println(stringArgumentCaptor.capture()); 753 List<String> strings = stringArgumentCaptor.getAllValues(); 754 assertThat(strings.get(5)).startsWith("mLastFewDispatchedV2KeyEvents:"); 755 } 756 757 @Test dump_containsLastFewKeyEvents()758 public void dump_containsLastFewKeyEvents() { 759 // Arrange 760 subscribeListener(); 761 ArgumentCaptor<String> stringArgumentCaptor = ArgumentCaptor.forClass(String.class); 762 dispatchMultipleV2KeyEvents(KeyEvent.KEYCODE_HOME, /* times= */ 1); 763 dispatchMultipleV2KeyEvents(KeyEvent.KEYCODE_BACK, /* times= */ 9); 764 dispatchMultipleV2KeyEvents(KeyEvent.KEYCODE_VOLUME_UP, /* times= */ 1); 765 766 // Act 767 mInputHalService.dump(mMockPrintWriter); 768 769 // Assert 770 verify(mMockPrintWriter, atLeastOnce()).println(stringArgumentCaptor.capture()); 771 List<String> strings = stringArgumentCaptor.getAllValues(); 772 int numHomeEvents = 0; 773 int numBackEvents = 0; 774 int numVolUpEvents = 0; 775 assertThat(strings.get(5)).startsWith("mLastFewDispatchedV2KeyEvents:"); 776 for (int i = 5; i < strings.size(); i++) { 777 if (strings.get(i).contains("keyCode=KEYCODE_HOME")) { 778 numHomeEvents++; 779 } else if (strings.get(i).contains("keyCode=KEYCODE_BACK")) { 780 numBackEvents++; 781 } else if (strings.get(i).contains("keyCode=KEYCODE_VOLUME_UP")) { 782 numVolUpEvents++; 783 } 784 } 785 assertWithMessage("Number of home events").that(numHomeEvents).isEqualTo(0); 786 assertWithMessage("Number of back events").that(numBackEvents).isEqualTo(9); 787 assertWithMessage("Number of volume up events").that(numVolUpEvents).isEqualTo(1); 788 } 789 790 @Test dump_containsLastFewMotionEvents()791 public void dump_containsLastFewMotionEvents() { 792 // Arrange 793 subscribeListener(); 794 ArgumentCaptor<String> stringArgumentCaptor = ArgumentCaptor.forClass(String.class); 795 dispatchMultipleMotionEvents(VehicleHwMotionInputAction.ACTION_DOWN, /* times= */ 1); 796 dispatchMultipleMotionEvents(VehicleHwMotionInputAction.ACTION_MOVE, /* times= */ 9); 797 dispatchMultipleMotionEvents(VehicleHwMotionInputAction.ACTION_UP, /* times= */ 1); 798 799 // Act 800 mInputHalService.dump(mMockPrintWriter); 801 802 // Assert 803 verify(mMockPrintWriter, atLeastOnce()).println(stringArgumentCaptor.capture()); 804 List<String> strings = stringArgumentCaptor.getAllValues(); 805 int numMoveEvents = 0; 806 int numUpEvents = 0; 807 int numDownEvents = 0; 808 assertThat(strings.get(6)).startsWith("mLastFewDispatchedMotionEvents:"); 809 for (int i = 6; i < strings.size(); i++) { 810 if (strings.get(i).contains("action=ACTION_DOWN")) { 811 numDownEvents++; 812 } else if (strings.get(i).contains("action=ACTION_MOVE")) { 813 numMoveEvents++; 814 } else if (strings.get(i).contains("action=ACTION_UP")) { 815 numUpEvents++; 816 } 817 } 818 // Number of down events should be 0 as only 10 recent events are stored. 819 assertWithMessage("Number of down events").that(numDownEvents).isEqualTo(0); 820 assertWithMessage("Number of move events").that(numMoveEvents).isEqualTo(9); 821 assertWithMessage("Number of up events").that(numUpEvents).isEqualTo(1); 822 } 823 dispatchMultipleMotionEvents(@ehicleHwMotionInputAction int action, int times)824 private void dispatchMultipleMotionEvents(@VehicleHwMotionInputAction int action, int times) { 825 for (int i = 0; i < times; i++) { 826 dispatchSinglePerSeatMotionEvent( 827 /* seat= */ 1, 828 VehicleDisplay.MAIN, 829 CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 830 VehicleHwMotionInputSource.SOURCE_TOUCHSCREEN, 831 action, 832 VehicleHwMotionButtonStateFlag.BUTTON_PRIMARY, 833 /* pointerCount= */ 2, 834 /* pointerIds= */ new int[]{10, 20}, 835 /* toolTypes= */ new int[]{VehicleHwMotionToolType.TOOL_TYPE_FINGER, 836 VehicleHwMotionToolType.TOOL_TYPE_MOUSE}, 837 /* xData= */ new float[]{100, 200}, 838 /* yData= */ new float[]{300, 400}, 839 /* pressureData= */ new float[]{1, 2}, 840 /* sizeData= */ new float[]{3, 4}, 841 /* downTimeNanos= */ SECONDS.toNanos(7), 842 /* timeStampNanos= */ SECONDS.toNanos(8) 843 ); 844 } 845 } 846 dispatchMultipleV2KeyEvents(int keyCode, int times)847 private void dispatchMultipleV2KeyEvents(int keyCode, int times) { 848 for (int i = 0; i < times; i++) { 849 dispatchSinglePerSeatEvent( 850 /* seat= */ 1, 851 VehicleDisplay.MAIN, 852 CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 853 keyCode, 854 Key.DOWN, 855 /* repeatCount= */ 1, 856 /* downTime= */ SECONDS.toNanos(7), 857 /* timeStampNanos= */ SECONDS.toNanos(9) 858 ); 859 } 860 } 861 subscribeListener()862 private void subscribeListener() { 863 mInputHalService.takeProperties(Set.of(HW_KEY_INPUT_CONFIG)); 864 assertThat(mInputHalService.isKeyInputSupported()).isTrue(); 865 866 mInputHalService.setInputListener(mInputListener); 867 verify(mVehicleHal).subscribePropertySafe(mInputHalService, VehicleProperty.HW_KEY_INPUT); 868 } 869 dispatchSingleEvent(Key action, int code, int actualDisplay, @DisplayTypeEnum int expectedDisplay)870 private KeyEvent dispatchSingleEvent(Key action, int code, int actualDisplay, 871 @DisplayTypeEnum int expectedDisplay) { 872 ArgumentCaptor<KeyEvent> captor = ArgumentCaptor.forClass(KeyEvent.class); 873 reset(mInputListener); 874 mInputHalService.onHalEvents( 875 List.of(makeKeyPropValue(action, code, actualDisplay))); 876 verify(mInputListener).onKeyEvent(captor.capture(), eq(expectedDisplay)); 877 reset(mInputListener); 878 return captor.getValue(); 879 } 880 dispatchSinglePerSeatEvent(int seat, int actualDisplay, @DisplayTypeEnum int expectedDisplay, int code, Key action, int repeatCount, long downTime, long timeStampNanos)881 private KeyEvent dispatchSinglePerSeatEvent(int seat, int actualDisplay, 882 @DisplayTypeEnum int expectedDisplay, int code, Key action, int repeatCount, 883 long downTime, long timeStampNanos) { 884 ArgumentCaptor<KeyEvent> captor = ArgumentCaptor.forClass(KeyEvent.class); 885 reset(mInputListener); 886 887 int actionValue = (action == Key.DOWN 888 ? VehicleHwKeyInputAction.ACTION_DOWN 889 : VehicleHwKeyInputAction.ACTION_UP); 890 mInputHalService.onHalEvents( 891 List.of(mPropValueBuilder.build(VehicleProperty.HW_KEY_INPUT_V2, 892 /* areaId= */ seat, 893 /* timestamp= */ timeStampNanos, 894 VehiclePropertyStatus.AVAILABLE, 895 /* int32Values= */ new int[] {actualDisplay, code, actionValue, 896 repeatCount}, 897 /* floatValues= */ new float[] {}, 898 /* int64Values= */ new long[] {downTime}, 899 /* stringValue= */ "", 900 /* byteValues= */ new byte[] {} 901 ))); 902 verify(mInputListener).onKeyEvent(captor.capture(), eq(expectedDisplay), 903 eq(seat)); 904 reset(mInputListener); 905 return captor.getValue(); 906 } 907 dispatchSinglePerSeatMotionEvent(int seat, int actualDisplay, @DisplayTypeEnum int expectedDisplay, int inputSource, int motionActionCode, int buttonState, int pointerCount, int[] pointerIds, int[] toolTypes, float[] xData, float[] yData, float[] pressureData, float[] sizeData, long downTimeNanos, long timeStampNanos)908 private MotionEvent dispatchSinglePerSeatMotionEvent(int seat, int actualDisplay, 909 @DisplayTypeEnum int expectedDisplay, int inputSource, int motionActionCode, 910 int buttonState, int pointerCount, int[] pointerIds, int[] toolTypes, float[] xData, 911 float[] yData, float[] pressureData, float[] sizeData, long downTimeNanos, 912 long timeStampNanos) { 913 ArgumentCaptor<MotionEvent> captor = ArgumentCaptor.forClass(MotionEvent.class); 914 reset(mInputListener); 915 916 int[] int32Array = new int[5 + 2 * pointerCount]; 917 float[] floatArray = new float[4 * pointerCount]; 918 int32Array[0] = actualDisplay; 919 int32Array[1] = inputSource; 920 int32Array[2] = motionActionCode; 921 int32Array[3] = buttonState; 922 int32Array[4] = pointerCount; 923 for (int i = 0; i < pointerCount; i++) { 924 int32Array[5 + i] = pointerIds[i]; 925 int32Array[5 + pointerCount + i] = toolTypes[i]; 926 floatArray[i] = xData[i]; 927 floatArray[pointerCount + i] = yData[i]; 928 floatArray[2 * pointerCount + i] = pressureData[i]; 929 floatArray[3 * pointerCount + i] = sizeData[i]; 930 } 931 932 mInputHalService.onHalEvents( 933 List.of(mPropValueBuilder.build(VehicleProperty.HW_MOTION_INPUT, 934 /* areaId = */ seat, 935 /* timestamp= */ timeStampNanos, 936 VehiclePropertyStatus.AVAILABLE, 937 int32Array, 938 floatArray, 939 /* int64Values= */ new long[] {downTimeNanos}, 940 /* stringValue= */ "", 941 /* byteValues= */ new byte[] {} 942 ))); 943 verify(mInputListener).onMotionEvent(captor.capture(), eq(expectedDisplay), eq(seat)); 944 reset(mInputListener); 945 return captor.getValue(); 946 } 947 dispatchSingleEventWithIndents(int code, int indents, int actualDisplay, @DisplayTypeEnum int expectedDisplay)948 private KeyEvent dispatchSingleEventWithIndents(int code, int indents, int actualDisplay, 949 @DisplayTypeEnum int expectedDisplay) { 950 ArgumentCaptor<KeyEvent> captor = ArgumentCaptor.forClass(KeyEvent.class); 951 reset(mInputListener); 952 mInputHalService.onHalEvents( 953 List.of(makeKeyPropValueWithIndents(code, indents, actualDisplay))); 954 verify(mInputListener, times(indents)).onKeyEvent(captor.capture(), 955 eq(expectedDisplay)); 956 reset(mInputListener); 957 return captor.getValue(); 958 } 959 makeKeyPropValue(Key action, int code, @DisplayTypeEnum int targetDisplayType)960 private HalPropValue makeKeyPropValue(Key action, int code, 961 @DisplayTypeEnum int targetDisplayType) { 962 int actionValue = (action == Key.DOWN 963 ? VehicleHwKeyInputAction.ACTION_DOWN 964 : VehicleHwKeyInputAction.ACTION_UP); 965 return mPropValueBuilder.build(VehicleProperty.HW_KEY_INPUT, /* areaId= */ 0, 966 new int[]{actionValue, code, targetDisplayType}); 967 } 968 makeKeyPropValueWithIndents(int code, int indents, @DisplayTypeEnum int targetDisplayType)969 private HalPropValue makeKeyPropValueWithIndents(int code, int indents, 970 @DisplayTypeEnum int targetDisplayType) { 971 // Only Key.down can have indents. 972 return mPropValueBuilder.build(VehicleProperty.HW_KEY_INPUT, /* areaId= */ 0, 973 new int[]{VehicleHwKeyInputAction.ACTION_DOWN, code, targetDisplayType, indents}); 974 } 975 makeRotaryPropValue(int rotaryInputType, int detents, long timestamp, int delayBetweenDetents, @DisplayTypeEnum int targetDisplayType)976 private HalPropValue makeRotaryPropValue(int rotaryInputType, int detents, long timestamp, 977 int delayBetweenDetents, @DisplayTypeEnum int targetDisplayType) { 978 ArrayList<Integer> int32Values = new ArrayList<>(); 979 int32Values.add(rotaryInputType); 980 int32Values.add(detents); 981 int32Values.add(targetDisplayType); 982 for (int i = 0; i < Math.abs(detents) - 1; i++) { 983 int32Values.add(delayBetweenDetents); 984 } 985 return mPropValueBuilder.build(VehicleProperty.HW_ROTARY_INPUT, /* areaId= */ 0, timestamp, 986 /*status=*/0, toIntArray(int32Values)); 987 } 988 makeCustomInputPropValue(int inputCode, @DisplayTypeEnum int targetDisplayType, int repeatCounter)989 private HalPropValue makeCustomInputPropValue(int inputCode, 990 @DisplayTypeEnum int targetDisplayType, int repeatCounter) { 991 return mPropValueBuilder.build(VehicleProperty.HW_CUSTOM_INPUT, /* areaId= */ 0, 992 new int[]{inputCode, targetDisplayType, repeatCounter}); 993 } 994 } 995