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