1 /* 2 * Copyright (C) 2020 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 17 package com.android.car.input; 18 19 import static android.car.CarOccupantZoneManager.DisplayTypeEnum; 20 import static android.hardware.automotive.vehicle.V2_0.CustomInputType.CUSTOM_EVENT_F1; 21 22 import static com.google.common.truth.Truth.assertThat; 23 import static com.google.common.truth.Truth.assertWithMessage; 24 25 import static org.mockito.ArgumentMatchers.any; 26 import static org.mockito.Mockito.spy; 27 import static org.mockito.Mockito.verify; 28 import static org.testng.Assert.assertThrows; 29 30 import android.annotation.NonNull; 31 import android.car.Car; 32 import android.car.CarOccupantZoneManager; 33 import android.car.input.CarInputManager; 34 import android.car.input.CustomInputEvent; 35 import android.car.input.RotaryEvent; 36 import android.hardware.automotive.vehicle.V2_0.VehicleDisplay; 37 import android.hardware.automotive.vehicle.V2_0.VehicleProperty; 38 import android.os.SystemClock; 39 import android.util.Log; 40 import android.util.Pair; 41 import android.view.KeyEvent; 42 43 import androidx.test.ext.junit.runners.AndroidJUnit4; 44 import androidx.test.filters.MediumTest; 45 46 import com.android.car.CarServiceUtils; 47 import com.android.car.MockedCarTestBase; 48 import com.android.car.vehiclehal.VehiclePropValueBuilder; 49 import com.android.internal.annotations.GuardedBy; 50 51 import org.junit.Test; 52 import org.junit.runner.RunWith; 53 54 import java.util.Arrays; 55 import java.util.Collections; 56 import java.util.LinkedList; 57 import java.util.List; 58 import java.util.concurrent.Executor; 59 import java.util.concurrent.Executors; 60 import java.util.concurrent.Semaphore; 61 import java.util.concurrent.TimeUnit; 62 63 64 // TODO(b/150818155): Add test cases to cover scenarios where different callbacks are registered 65 // against different display types (e.g. main and cluster) 66 @RunWith(AndroidJUnit4.class) 67 @MediumTest 68 public final class CarInputManagerTest extends MockedCarTestBase { 69 private static final String TAG = CarInputManagerTest.class.getSimpleName(); 70 71 private static final int INVALID_DISPLAY_TYPE = -1; 72 private static final int INVALID_INPUT_TYPE = -1; 73 74 private CarInputManager mCarInputManager; 75 76 private final class CaptureCallback implements CarInputManager.CarInputCaptureCallback { 77 78 private static final long EVENT_WAIT_TIME = 5_000; 79 80 private final Object mLock = new Object(); 81 82 private final String mName; 83 CaptureCallback(String name)84 private CaptureCallback(String name) { 85 mName = name; 86 } 87 88 // Stores passed events. Last one in front 89 @GuardedBy("mLock") 90 private final LinkedList<Pair<Integer, List<KeyEvent>>> mKeyEvents = new LinkedList<>(); 91 92 // Stores passed events. Last one in front 93 @GuardedBy("mLock") 94 private final LinkedList<Pair<Integer, List<RotaryEvent>>> mRotaryEvents = 95 new LinkedList<>(); 96 97 // Stores passed events. Last one in front 98 @GuardedBy("mLock") 99 private final LinkedList<Pair<Integer, List<CustomInputEvent>>> mCustomInputEvents = 100 new LinkedList<>(); 101 102 // Stores passed state changes. Last one in front 103 @GuardedBy("mLock") 104 private final LinkedList<Pair<Integer, int[]>> mStateChanges = new LinkedList<>(); 105 106 private final Semaphore mKeyEventWait = new Semaphore(0); 107 private final Semaphore mRotaryEventWait = new Semaphore(0); 108 private final Semaphore mStateChangeWait = new Semaphore(0); 109 private final Semaphore mCustomInputEventWait = new Semaphore(0); 110 111 @Override onKeyEvents(@isplayTypeEnum int targetDisplayType, @NonNull List<KeyEvent> keyEvents)112 public void onKeyEvents(@DisplayTypeEnum int targetDisplayType, 113 @NonNull List<KeyEvent> keyEvents) { 114 Log.i(TAG, "onKeyEvents event:" + keyEvents.get(0) + " this:" + this); 115 synchronized (mLock) { 116 mKeyEvents.addFirst(new Pair<>(targetDisplayType, keyEvents)); 117 } 118 mKeyEventWait.release(); 119 } 120 121 @Override onRotaryEvents(@isplayTypeEnum int targetDisplayType, @NonNull List<RotaryEvent> events)122 public void onRotaryEvents(@DisplayTypeEnum int targetDisplayType, 123 @NonNull List<RotaryEvent> events) { 124 Log.i(TAG, "onRotaryEvents event:" + events.get(0) + " this:" + this); 125 synchronized (mLock) { 126 mRotaryEvents.addFirst(new Pair<>(targetDisplayType, events)); 127 } 128 mRotaryEventWait.release(); 129 } 130 131 @Override onCustomInputEvents(@isplayTypeEnum int targetDisplayType, @NonNull List<CustomInputEvent> events)132 public void onCustomInputEvents(@DisplayTypeEnum int targetDisplayType, 133 @NonNull List<CustomInputEvent> events) { 134 Log.i(TAG, "onCustomInputEvents event:" + events.get(0) + " this:" + this); 135 synchronized (mLock) { 136 mCustomInputEvents.addFirst(new Pair<>(targetDisplayType, events)); 137 } 138 mCustomInputEventWait.release(); 139 } 140 141 @Override onCaptureStateChanged(@isplayTypeEnum int targetDisplayType, @NonNull @CarInputManager.InputTypeEnum int[] activeInputTypes)142 public void onCaptureStateChanged(@DisplayTypeEnum int targetDisplayType, 143 @NonNull @CarInputManager.InputTypeEnum int[] activeInputTypes) { 144 Log.i(TAG, "onCaptureStateChanged types:" + Arrays.toString(activeInputTypes) 145 + " this:" + this); 146 synchronized (mLock) { 147 mStateChanges.addFirst(new Pair<>(targetDisplayType, activeInputTypes)); 148 } 149 mStateChangeWait.release(); 150 } 151 resetAllEventsWaiting()152 private void resetAllEventsWaiting() { 153 mStateChangeWait.drainPermits(); 154 mKeyEventWait.drainPermits(); 155 mRotaryEventWait.drainPermits(); 156 } 157 waitForStateChange()158 private void waitForStateChange() throws Exception { 159 assertWithMessage("Failed to acquire semaphore in %s ms", EVENT_WAIT_TIME).that( 160 mStateChangeWait.tryAcquire(EVENT_WAIT_TIME, TimeUnit.MILLISECONDS)).isTrue(); 161 } 162 waitForKeyEvent()163 private void waitForKeyEvent() throws Exception { 164 assertWithMessage("Failed to acquire semaphore in %s ms", EVENT_WAIT_TIME).that( 165 mKeyEventWait.tryAcquire(EVENT_WAIT_TIME, TimeUnit.MILLISECONDS)).isTrue(); 166 } 167 waitForRotaryEvent()168 private void waitForRotaryEvent() throws Exception { 169 assertWithMessage("Failed to acquire semaphore in %s ms", EVENT_WAIT_TIME).that( 170 mRotaryEventWait.tryAcquire(EVENT_WAIT_TIME, TimeUnit.MILLISECONDS)).isTrue(); 171 } 172 waitForCustomInputEvent()173 private void waitForCustomInputEvent() throws Exception { 174 assertWithMessage("Failed to acquire semaphore in %s ms", EVENT_WAIT_TIME).that( 175 mCustomInputEventWait.tryAcquire( 176 EVENT_WAIT_TIME, TimeUnit.MILLISECONDS)).isTrue(); 177 } 178 getkeyEvents()179 private LinkedList<Pair<Integer, List<KeyEvent>>> getkeyEvents() { 180 synchronized (mLock) { 181 LinkedList<Pair<Integer, List<KeyEvent>>> r = 182 new LinkedList<>(mKeyEvents); 183 Log.i(TAG, "getKeyEvents size:" + r.size() + ",this:" + this); 184 return r; 185 } 186 } 187 getRotaryEvents()188 private LinkedList<Pair<Integer, List<RotaryEvent>>> getRotaryEvents() { 189 synchronized (mLock) { 190 LinkedList<Pair<Integer, List<RotaryEvent>>> r = 191 new LinkedList<>(mRotaryEvents); 192 Log.i(TAG, "getRotaryEvents size:" + r.size() + ",this:" + this); 193 return r; 194 } 195 } 196 getStateChanges()197 private LinkedList<Pair<Integer, int[]>> getStateChanges() { 198 synchronized (mLock) { 199 return new LinkedList<>(mStateChanges); 200 } 201 } 202 getCustomInputEvents()203 private LinkedList<Pair<Integer, List<CustomInputEvent>>> getCustomInputEvents() { 204 synchronized (mLock) { 205 LinkedList<Pair<Integer, List<CustomInputEvent>>> r = 206 new LinkedList<>(mCustomInputEvents); 207 Log.i(TAG, "getCustomInputEvents size:" + r.size() + ",this:" + this); 208 return r; 209 } 210 } 211 212 @Override toString()213 public String toString() { 214 return "CaptureCallback{mName='" + mName + "'}"; 215 } 216 } 217 218 private final CaptureCallback mCallback0 = new CaptureCallback("callback0"); 219 private final CaptureCallback mCallback1 = new CaptureCallback("callback1"); 220 private final CaptureCallback mCallback2 = new CaptureCallback("callback2"); 221 222 @Override configureMockedHal()223 protected synchronized void configureMockedHal() { 224 addProperty(VehicleProperty.HW_KEY_INPUT, 225 VehiclePropValueBuilder.newBuilder(VehicleProperty.HW_KEY_INPUT) 226 .addIntValue(0, 0, 0) 227 .build()); 228 addProperty(VehicleProperty.HW_ROTARY_INPUT, 229 VehiclePropValueBuilder.newBuilder(VehicleProperty.HW_ROTARY_INPUT) 230 .addIntValue(0, 1, 0) 231 .build()); 232 addProperty(VehicleProperty.HW_CUSTOM_INPUT, 233 VehiclePropValueBuilder.newBuilder(VehicleProperty.HW_CUSTOM_INPUT) 234 .addIntValue(0) 235 .build()); 236 } 237 238 @Override configureResourceOverrides(MockResources resources)239 protected synchronized void configureResourceOverrides(MockResources resources) { 240 super.configureResourceOverrides(resources); 241 resources.overrideResource(com.android.car.R.string.config_clusterHomeActivity, 242 getTestContext().getPackageName() + "/" + CarInputManagerTest.class.getName()); 243 } 244 245 @Override setUp()246 public void setUp() throws Exception { 247 super.setUp(); 248 mCarInputManager = (CarInputManager) getCar().getCarManager(Car.CAR_INPUT_SERVICE); 249 assertThat(mCarInputManager).isNotNull(); 250 } 251 252 @Test testInvalidArgs()253 public void testInvalidArgs() { 254 // Invalid display 255 assertThrows(IllegalArgumentException.class, 256 () -> mCarInputManager.requestInputEventCapture(INVALID_DISPLAY_TYPE, 257 new int[]{CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION}, 0, mCallback0)); 258 259 // Invalid input types 260 assertThrows(IllegalArgumentException.class, 261 () -> mCarInputManager.requestInputEventCapture( 262 CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 263 new int[]{CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION, INVALID_INPUT_TYPE}, 264 0, mCallback0)); 265 assertThrows(IllegalArgumentException.class, 266 () -> mCarInputManager.requestInputEventCapture( 267 CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 268 new int[]{CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION}, 269 CarInputManager.CAPTURE_REQ_FLAGS_TAKE_ALL_EVENTS_FOR_DISPLAY, mCallback0)); 270 assertThrows(IllegalArgumentException.class, 271 () -> mCarInputManager.requestInputEventCapture( 272 CarOccupantZoneManager.DISPLAY_TYPE_MAIN, new int[]{INVALID_INPUT_TYPE}, 273 0, mCallback0)); 274 assertThrows(IllegalArgumentException.class, 275 () -> mCarInputManager.requestInputEventCapture( 276 CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 277 new int[]{CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION, INVALID_INPUT_TYPE}, 278 0, mCallback0)); 279 } 280 createAnotherCarInputManager()281 private CarInputManager createAnotherCarInputManager() { 282 return (CarInputManager) createNewCar().getCarManager(Car.CAR_INPUT_SERVICE); 283 } 284 285 @Test testInjectKeyEvent_mainDisplay()286 public void testInjectKeyEvent_mainDisplay() throws Exception { 287 int r = mCarInputManager.requestInputEventCapture( 288 CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 289 new int[]{CarInputManager.INPUT_TYPE_ALL_INPUTS}, 290 CarInputManager.CAPTURE_REQ_FLAGS_TAKE_ALL_EVENTS_FOR_DISPLAY, mCallback0); 291 assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED); 292 293 KeyEvent keyEvent = newKeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER); 294 295 mCarInputManager.injectKeyEvent(keyEvent, CarOccupantZoneManager.DISPLAY_TYPE_MAIN); 296 297 mCallback0.waitForKeyEvent(); 298 assertThat(mCallback0.getkeyEvents()).containsExactly( 299 new Pair<>(CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 300 Collections.singletonList(keyEvent))); 301 } 302 303 @Test testInjectKeyEvent_instrumentClusterDisplay()304 public void testInjectKeyEvent_instrumentClusterDisplay() throws Exception { 305 int r = mCarInputManager.requestInputEventCapture( 306 CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER, 307 new int[]{CarInputManager.INPUT_TYPE_ALL_INPUTS}, 308 CarInputManager.CAPTURE_REQ_FLAGS_TAKE_ALL_EVENTS_FOR_DISPLAY, mCallback0); 309 assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED); 310 311 KeyEvent keyEvent = newKeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER); 312 313 mCarInputManager.injectKeyEvent(keyEvent, 314 CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER); 315 316 mCallback0.waitForKeyEvent(); 317 assertThat(mCallback0.getkeyEvents()).containsExactly( 318 new Pair<>(CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER, 319 Collections.singletonList(keyEvent))); 320 } 321 newKeyEvent(int action, int code)322 private static KeyEvent newKeyEvent(int action, int code) { 323 long currentTime = SystemClock.uptimeMillis(); 324 return new KeyEvent(/* downTime= */ currentTime, 325 /* eventTime= */ currentTime, action, code, 326 /* repeat= */ 0); 327 } 328 329 @Test testFailWithFullCaptureHigherPriority()330 public void testFailWithFullCaptureHigherPriority() { 331 CarInputManager carInputManager0 = createAnotherCarInputManager(); 332 int r = carInputManager0.requestInputEventCapture( 333 CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 334 new int[]{CarInputManager.INPUT_TYPE_ALL_INPUTS}, 335 CarInputManager.CAPTURE_REQ_FLAGS_TAKE_ALL_EVENTS_FOR_DISPLAY, mCallback0); 336 assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED); 337 338 //TODO test event 339 340 r = mCarInputManager.requestInputEventCapture( 341 CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 342 new int[]{CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION}, 0, mCallback1); 343 assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_FAILED); 344 345 carInputManager0.releaseInputEventCapture(CarOccupantZoneManager.DISPLAY_TYPE_MAIN); 346 347 r = mCarInputManager.requestInputEventCapture( 348 CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 349 new int[]{CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION}, 0, mCallback1); 350 assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED); 351 352 //TODO test event 353 } 354 355 @Test testDelayedGrantWithFullCapture()356 public void testDelayedGrantWithFullCapture() throws Exception { 357 mCallback1.resetAllEventsWaiting(); 358 CarInputManager carInputManager0 = createAnotherCarInputManager(); 359 int r = carInputManager0.requestInputEventCapture( 360 CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 361 new int[]{CarInputManager.INPUT_TYPE_ALL_INPUTS}, 362 CarInputManager.CAPTURE_REQ_FLAGS_TAKE_ALL_EVENTS_FOR_DISPLAY, mCallback0); 363 assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED); 364 365 injectKeyEvent(true, KeyEvent.KEYCODE_NAVIGATE_NEXT); 366 mCallback0.waitForKeyEvent(); 367 assertLastKeyEvent(CarOccupantZoneManager.DISPLAY_TYPE_MAIN, true, 368 KeyEvent.KEYCODE_NAVIGATE_NEXT, mCallback0); 369 370 injectKeyEvent(true, KeyEvent.KEYCODE_DPAD_CENTER); 371 mCallback0.waitForKeyEvent(); 372 assertLastKeyEvent(CarOccupantZoneManager.DISPLAY_TYPE_MAIN, true, 373 KeyEvent.KEYCODE_DPAD_CENTER, mCallback0); 374 375 int numClicks = 3; 376 injectRotaryEvent(VehicleDisplay.MAIN, numClicks); 377 mCallback0.waitForRotaryEvent(); 378 assertLastRotaryEvent(CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 379 CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION, numClicks, mCallback0); 380 381 r = mCarInputManager.requestInputEventCapture( 382 CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 383 new int[]{CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION}, 384 CarInputManager.CAPTURE_REQ_FLAGS_ALLOW_DELAYED_GRANT, mCallback1); 385 assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_DELAYED); 386 387 injectRotaryEvent(VehicleDisplay.MAIN, numClicks); 388 waitForDispatchToMain(); 389 assertNumberOfOnRotaryEvents(0, mCallback1); 390 391 carInputManager0.releaseInputEventCapture(CarOccupantZoneManager.DISPLAY_TYPE_MAIN); 392 393 // Now capture should be granted back 394 mCallback1.waitForStateChange(); 395 assertLastStateChange(CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 396 new int[]{ 397 CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION}, 398 mCallback1); 399 assertNoStateChange(mCallback0); 400 401 injectRotaryEvent(VehicleDisplay.MAIN, numClicks); 402 mCallback1.waitForRotaryEvent(); 403 assertLastRotaryEvent(CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 404 CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION, numClicks, mCallback1); 405 } 406 407 @Test testOneClientTransitionFromFullToNonFull()408 public void testOneClientTransitionFromFullToNonFull() { 409 CarInputManager carInputManager0 = createAnotherCarInputManager(); 410 411 int r = carInputManager0.requestInputEventCapture( 412 CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 413 new int[]{CarInputManager.INPUT_TYPE_ALL_INPUTS}, 414 CarInputManager.CAPTURE_REQ_FLAGS_TAKE_ALL_EVENTS_FOR_DISPLAY, mCallback0); 415 assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED); 416 417 r = mCarInputManager.requestInputEventCapture( 418 CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 419 new int[]{ 420 CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION, 421 CarInputManager.INPUT_TYPE_NAVIGATE_KEYS}, 422 CarInputManager.CAPTURE_REQ_FLAGS_ALLOW_DELAYED_GRANT, mCallback1); 423 assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_DELAYED); 424 425 r = carInputManager0.requestInputEventCapture( 426 CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 427 new int[]{ 428 CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION, 429 CarInputManager.INPUT_TYPE_DPAD_KEYS}, 0, mCallback0); 430 assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED); 431 432 waitForDispatchToMain(); 433 assertLastStateChange(CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 434 new int[]{CarInputManager.INPUT_TYPE_NAVIGATE_KEYS}, 435 mCallback1); 436 assertNoStateChange(mCallback0); 437 } 438 439 @Test testSwitchFromFullCaptureToPerTypeCapture()440 public void testSwitchFromFullCaptureToPerTypeCapture() { 441 int r = mCarInputManager.requestInputEventCapture( 442 CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 443 new int[]{CarInputManager.INPUT_TYPE_ALL_INPUTS}, 444 CarInputManager.CAPTURE_REQ_FLAGS_TAKE_ALL_EVENTS_FOR_DISPLAY, mCallback0); 445 assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED); 446 447 r = mCarInputManager.requestInputEventCapture( 448 CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 449 new int[]{CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION}, 0, mCallback1); 450 assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED); 451 452 r = mCarInputManager.requestInputEventCapture( 453 CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 454 new int[]{CarInputManager.INPUT_TYPE_CUSTOM_INPUT_EVENT}, 0, mCallback2); 455 assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED); 456 } 457 458 @Test testIndependentTwoCaptures()459 public void testIndependentTwoCaptures() throws Exception { 460 int r = createAnotherCarInputManager().requestInputEventCapture( 461 CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 462 new int[]{CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION}, 0, mCallback0); 463 assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED); 464 465 int numClicks = 3; 466 injectRotaryEvent(VehicleDisplay.MAIN, numClicks); 467 mCallback0.waitForRotaryEvent(); 468 assertLastRotaryEvent(CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 469 CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION, numClicks, mCallback0); 470 471 r = mCarInputManager.requestInputEventCapture( 472 CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 473 new int[]{CarInputManager.INPUT_TYPE_NAVIGATE_KEYS}, 0, mCallback1); 474 assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED); 475 476 injectKeyEvent(true, KeyEvent.KEYCODE_NAVIGATE_NEXT); 477 mCallback1.waitForKeyEvent(); 478 assertLastKeyEvent(CarOccupantZoneManager.DISPLAY_TYPE_MAIN, true, 479 KeyEvent.KEYCODE_NAVIGATE_NEXT, mCallback1); 480 } 481 482 @Test testTwoClientsOverwrap()483 public void testTwoClientsOverwrap() throws Exception { 484 CarInputManager carInputManager0 = createAnotherCarInputManager(); 485 CarInputManager carInputManager1 = createAnotherCarInputManager(); 486 487 mCallback0.resetAllEventsWaiting(); 488 int r = carInputManager0.requestInputEventCapture( 489 CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 490 new int[]{ 491 CarInputManager.INPUT_TYPE_DPAD_KEYS, 492 CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION}, 0, mCallback0); 493 assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED); 494 495 injectKeyEvent(true, KeyEvent.KEYCODE_DPAD_CENTER); 496 mCallback0.waitForKeyEvent(); 497 assertLastKeyEvent(CarOccupantZoneManager.DISPLAY_TYPE_MAIN, true, 498 KeyEvent.KEYCODE_DPAD_CENTER, mCallback0); 499 500 int numClicks = 3; 501 injectRotaryEvent(VehicleDisplay.MAIN, numClicks); 502 mCallback0.waitForRotaryEvent(); 503 assertLastRotaryEvent(CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 504 CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION, numClicks, mCallback0); 505 506 r = carInputManager1.requestInputEventCapture( 507 CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 508 new int[]{ 509 CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION, 510 CarInputManager.INPUT_TYPE_NAVIGATE_KEYS}, 0, mCallback1); 511 assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED); 512 513 mCallback0.waitForStateChange(); 514 assertLastStateChange(CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 515 new int[]{CarInputManager.INPUT_TYPE_DPAD_KEYS}, 516 mCallback0); 517 assertNoStateChange(mCallback1); 518 519 injectKeyEvent(true, KeyEvent.KEYCODE_NAVIGATE_NEXT); 520 mCallback1.waitForKeyEvent(); 521 assertLastKeyEvent(CarOccupantZoneManager.DISPLAY_TYPE_MAIN, true, 522 KeyEvent.KEYCODE_NAVIGATE_NEXT, mCallback1); 523 assertNumberOfOnKeyEvents(1, mCallback0); 524 525 injectKeyEvent(true, KeyEvent.KEYCODE_DPAD_CENTER); 526 mCallback0.waitForKeyEvent(); 527 assertLastKeyEvent(CarOccupantZoneManager.DISPLAY_TYPE_MAIN, true, 528 KeyEvent.KEYCODE_DPAD_CENTER, mCallback0); 529 assertNumberOfOnKeyEvents(2, mCallback0); 530 531 injectRotaryEvent(VehicleDisplay.MAIN, numClicks); 532 mCallback1.waitForRotaryEvent(); 533 assertLastRotaryEvent(CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 534 CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION, numClicks, mCallback1); 535 assertNumberOfOnRotaryEvents(1, mCallback0); 536 537 mCallback0.resetAllEventsWaiting(); 538 carInputManager1.releaseInputEventCapture(CarOccupantZoneManager.DISPLAY_TYPE_MAIN); 539 540 mCallback0.waitForStateChange(); 541 assertLastStateChange(CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 542 new int[]{CarInputManager.INPUT_TYPE_DPAD_KEYS, 543 CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION}, 544 mCallback0); 545 assertNoStateChange(mCallback1); 546 547 injectRotaryEvent(VehicleDisplay.MAIN, numClicks); 548 mCallback0.waitForRotaryEvent(); 549 assertLastRotaryEvent(CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 550 CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION, numClicks, mCallback0); 551 } 552 553 @Test testInteractionWithFullCapturer()554 public void testInteractionWithFullCapturer() { 555 CarInputManager carInputManager0 = createAnotherCarInputManager(); 556 CarInputManager carInputManager1 = createAnotherCarInputManager(); 557 558 Log.i(TAG, "requestInputEventCapture callback 0"); 559 int r = carInputManager0.requestInputEventCapture( 560 CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 561 new int[]{ 562 CarInputManager.INPUT_TYPE_DPAD_KEYS, 563 CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION}, 0, mCallback0); 564 assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED); 565 566 Log.i(TAG, "requestInputEventCapture callback 1"); 567 r = carInputManager1.requestInputEventCapture( 568 CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 569 new int[]{CarInputManager.INPUT_TYPE_ALL_INPUTS}, 570 CarInputManager.CAPTURE_REQ_FLAGS_TAKE_ALL_EVENTS_FOR_DISPLAY, mCallback1); 571 assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED); 572 573 waitForDispatchToMain(); 574 assertLastStateChange(CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 575 new int[0], mCallback0); 576 assertNoStateChange(mCallback1); 577 578 Log.i(TAG, "releaseInputEventCapture callback 1"); 579 carInputManager1.releaseInputEventCapture(CarOccupantZoneManager.DISPLAY_TYPE_MAIN); 580 581 waitForDispatchToMain(); 582 assertLastStateChange(CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 583 new int[]{CarInputManager.INPUT_TYPE_DPAD_KEYS, 584 CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION}, 585 mCallback0); 586 assertNoStateChange(mCallback1); 587 } 588 589 @Test testFullCapturerAcceptsNotMappedKey()590 public void testFullCapturerAcceptsNotMappedKey() throws Exception { 591 int r = mCarInputManager.requestInputEventCapture( 592 CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER, 593 new int[]{CarInputManager.INPUT_TYPE_ALL_INPUTS}, 594 CarInputManager.CAPTURE_REQ_FLAGS_TAKE_ALL_EVENTS_FOR_DISPLAY, mCallback0); 595 assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED); 596 597 injectKeyEvent(true, KeyEvent.KEYCODE_MENU, VehicleDisplay.INSTRUMENT_CLUSTER); 598 mCallback0.waitForKeyEvent(); 599 assertLastKeyEvent(CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER, true, 600 KeyEvent.KEYCODE_MENU, mCallback0); 601 } 602 603 @Test testSingleClientUpdates()604 public void testSingleClientUpdates() { 605 int r = mCarInputManager.requestInputEventCapture( 606 CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 607 new int[]{ 608 CarInputManager.INPUT_TYPE_DPAD_KEYS, 609 CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION}, 0, mCallback0); 610 assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED); 611 612 r = mCarInputManager.requestInputEventCapture( 613 CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 614 new int[]{ 615 CarInputManager.INPUT_TYPE_DPAD_KEYS, 616 CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION}, 0, mCallback0); 617 assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED); 618 619 waitForDispatchToMain(); 620 assertNoStateChange(mCallback0); 621 622 r = mCarInputManager.requestInputEventCapture( 623 CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 624 new int[]{ 625 CarInputManager.INPUT_TYPE_DPAD_KEYS, 626 CarInputManager.INPUT_TYPE_NAVIGATE_KEYS}, 0, mCallback0); 627 assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED); 628 629 waitForDispatchToMain(); 630 assertNoStateChange(mCallback0); 631 632 r = mCarInputManager.requestInputEventCapture( 633 CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 634 new int[]{CarInputManager.INPUT_TYPE_ALL_INPUTS}, 635 CarInputManager.CAPTURE_REQ_FLAGS_TAKE_ALL_EVENTS_FOR_DISPLAY, mCallback0); 636 assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED); 637 638 waitForDispatchToMain(); 639 assertNoStateChange(mCallback0); 640 641 r = mCarInputManager.requestInputEventCapture( 642 CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 643 new int[]{CarInputManager.INPUT_TYPE_ALL_INPUTS}, 644 CarInputManager.CAPTURE_REQ_FLAGS_TAKE_ALL_EVENTS_FOR_DISPLAY, mCallback0); 645 assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED); 646 647 waitForDispatchToMain(); 648 assertNoStateChange(mCallback0); 649 } 650 651 @Test testInjectingRotaryEventAndExecutor()652 public void testInjectingRotaryEventAndExecutor() throws Exception { 653 // Arrange executors to process events 654 Executor rotaryExecutor = spy(Executors.newSingleThreadExecutor()); 655 656 // Arrange: register callback 657 CarInputManager carInputManager = createAnotherCarInputManager(); 658 int r = carInputManager.requestInputEventCapture( 659 CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 660 new int[]{CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION}, 0, rotaryExecutor, 661 mCallback0); 662 assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED); 663 664 // Act: inject RotaryEvent 665 int numClicks = 3; 666 injectRotaryEvent(VehicleDisplay.MAIN, numClicks); 667 668 // Assert: ensure RotaryEvent was delivered 669 mCallback0.waitForRotaryEvent(); 670 assertLastRotaryEvent(CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 671 CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION, numClicks, mCallback0); 672 673 // Assert: ensure that Rotary event was dispatched using the assigned executor 674 verify(rotaryExecutor).execute(any(Runnable.class)); 675 } 676 677 @Test testInjectingKeyEventAndExecutor()678 public void testInjectingKeyEventAndExecutor() throws Exception { 679 // Arrange executors to process events 680 Executor keyEventExecutor = spy(Executors.newSingleThreadExecutor()); 681 682 // Arrange: register callback 683 CarInputManager carInputManager = createAnotherCarInputManager(); 684 int r = carInputManager.requestInputEventCapture( 685 CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 686 new int[]{CarInputManager.INPUT_TYPE_NAVIGATE_KEYS}, 0, keyEventExecutor, 687 mCallback0); 688 assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED); 689 690 // Act: inject KeyEvent 691 injectKeyEvent(true, KeyEvent.KEYCODE_NAVIGATE_NEXT); 692 693 // Assert: ensure KeyEvent was delivered 694 mCallback0.waitForKeyEvent(); 695 assertLastKeyEvent(CarOccupantZoneManager.DISPLAY_TYPE_MAIN, true, 696 KeyEvent.KEYCODE_NAVIGATE_NEXT, mCallback0); 697 698 // Assert: ensure that Key event was dispatched using the assigned executor 699 verify(keyEventExecutor).execute(any(Runnable.class)); 700 } 701 702 @Test testInjectingCustomInputEventAndExecutor()703 public void testInjectingCustomInputEventAndExecutor() throws Exception { 704 // Arrange executors to process events 705 Executor customInputEventExecutor = spy(Executors.newSingleThreadExecutor()); 706 707 // Arrange: register callback 708 CarInputManager carInputManager = createAnotherCarInputManager(); 709 int r = carInputManager.requestInputEventCapture( 710 CarOccupantZoneManager.DISPLAY_TYPE_MAIN, 711 new int[]{CarInputManager.INPUT_TYPE_CUSTOM_INPUT_EVENT}, 0, 712 customInputEventExecutor, mCallback0); 713 assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED); 714 715 // Act: inject CustomInputEvent 716 int repeatedCounter = 1; 717 injectCustomInputEvent(CUSTOM_EVENT_F1, VehicleDisplay.MAIN, 718 /* repeatCounter= */ repeatedCounter); 719 720 // Assert: ensure CustomInputEvent was delivered 721 mCallback0.waitForCustomInputEvent(); 722 assertLastCustomInputEvent(CarOccupantZoneManager.DISPLAY_TYPE_MAIN, CUSTOM_EVENT_F1, 723 repeatedCounter, mCallback0); 724 725 // Assert: ensure that CustomInputEvent was dispatched using the assigned executor 726 verify(customInputEventExecutor).execute(any(Runnable.class)); 727 } 728 729 /** 730 * Events dispatched to main, so this should guarantee that all event dispatched are completed. 731 */ waitForDispatchToMain()732 private void waitForDispatchToMain() { 733 // Needs to be invoked twice as it is dispatched to main inside car service once and it is 734 // dispatched to main inside CarInputManager once. 735 CarServiceUtils.runOnMainSync(() -> {}); 736 CarServiceUtils.runOnMainSync(() -> {}); 737 } 738 assertLastKeyEvent(int displayTarget, boolean down, int keyCode, CaptureCallback callback)739 private void assertLastKeyEvent(int displayTarget, boolean down, int keyCode, 740 CaptureCallback callback) { 741 LinkedList<Pair<Integer, List<KeyEvent>>> events = callback.getkeyEvents(); 742 assertThat(events).isNotEmpty(); 743 Pair<Integer, List<KeyEvent>> lastEvent = events.getFirst(); 744 assertThat(lastEvent.first).isEqualTo(displayTarget); 745 assertThat(lastEvent.second).hasSize(1); 746 KeyEvent keyEvent = lastEvent.second.get(0); 747 assertThat(keyEvent.getAction()).isEqualTo( 748 down ? KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP); 749 assertThat(keyEvent.getKeyCode()).isEqualTo(keyCode); 750 } 751 assertNumberOfOnKeyEvents(int expectedNumber, CaptureCallback callback)752 private void assertNumberOfOnKeyEvents(int expectedNumber, CaptureCallback callback) { 753 LinkedList<Pair<Integer, List<KeyEvent>>> events = callback.getkeyEvents(); 754 assertThat(events).hasSize(expectedNumber); 755 } 756 assertLastRotaryEvent(int displayTarget, int rotaryType, int numberOfClicks, CaptureCallback callback)757 private void assertLastRotaryEvent(int displayTarget, int rotaryType, int numberOfClicks, 758 CaptureCallback callback) { 759 LinkedList<Pair<Integer, List<RotaryEvent>>> rotaryEvents = callback.getRotaryEvents(); 760 assertThat(rotaryEvents).isNotEmpty(); 761 Pair<Integer, List<RotaryEvent>> lastEvent = rotaryEvents.getFirst(); 762 assertThat(lastEvent.first).isEqualTo(displayTarget); 763 assertThat(lastEvent.second).hasSize(1); 764 RotaryEvent rotaryEvent = lastEvent.second.get(0); 765 assertThat(rotaryEvent.getInputType()).isEqualTo(rotaryType); 766 assertThat(rotaryEvent.getNumberOfClicks()).isEqualTo(numberOfClicks); 767 // TODO(b/151225008) Test timestamp 768 } 769 assertNumberOfOnRotaryEvents(int expectedNumber, CaptureCallback callback)770 private void assertNumberOfOnRotaryEvents(int expectedNumber, CaptureCallback callback) { 771 LinkedList<Pair<Integer, List<RotaryEvent>>> rotaryEvents = callback.getRotaryEvents(); 772 assertThat(rotaryEvents).hasSize(expectedNumber); 773 } 774 assertLastStateChange(int expectedTargetDisplayTarget, int[] expectedInputTypes, CaptureCallback callback)775 private void assertLastStateChange(int expectedTargetDisplayTarget, int[] expectedInputTypes, 776 CaptureCallback callback) { 777 LinkedList<Pair<Integer, int[]>> changes = callback.getStateChanges(); 778 assertThat(changes).isNotEmpty(); 779 Pair<Integer, int[]> lastChange = changes.getFirst(); 780 assertStateChange(expectedTargetDisplayTarget, expectedInputTypes, lastChange); 781 } 782 assertNoStateChange(CaptureCallback callback)783 private void assertNoStateChange(CaptureCallback callback) { 784 assertThat(callback.getStateChanges()).isEmpty(); 785 } 786 assertStateChange(int expectedTargetDisplayTarget, int[] expectedInputTypes, Pair<Integer, int[]> actual)787 private void assertStateChange(int expectedTargetDisplayTarget, int[] expectedInputTypes, 788 Pair<Integer, int[]> actual) { 789 Arrays.sort(expectedInputTypes); 790 assertThat(actual.first).isEqualTo(expectedTargetDisplayTarget); 791 assertThat(actual.second).isEqualTo(expectedInputTypes); 792 } 793 assertLastCustomInputEvent(int expectedDisplayType, int expectedCustomEventFunction, int expectedRepeatedCounter, CaptureCallback callback)794 private void assertLastCustomInputEvent(int expectedDisplayType, 795 int expectedCustomEventFunction, int expectedRepeatedCounter, 796 CaptureCallback callback) { 797 LinkedList<Pair<Integer, List<CustomInputEvent>>> events = callback.getCustomInputEvents(); 798 assertThat(events).isNotEmpty(); 799 800 Pair<Integer, List<CustomInputEvent>> lastEvent = events.getFirst(); 801 assertThat(lastEvent.first).isEqualTo(expectedDisplayType); 802 assertThat(lastEvent.second).hasSize(1); 803 804 CustomInputEvent event = lastEvent.second.get(0); 805 assertThat(event.getInputCode()).isEqualTo(expectedCustomEventFunction); 806 assertThat(event.getRepeatCounter()).isEqualTo(expectedRepeatedCounter); 807 assertThat(event.getTargetDisplayType()).isEqualTo(expectedDisplayType); 808 } 809 injectKeyEvent(boolean down, int keyCode)810 private void injectKeyEvent(boolean down, int keyCode) { 811 injectKeyEvent(down, keyCode, VehicleDisplay.MAIN); 812 } 813 injectKeyEvent(boolean down, int keyCode, int vehicleDisplayType)814 private void injectKeyEvent(boolean down, int keyCode, int vehicleDisplayType) { 815 getMockedVehicleHal().injectEvent( 816 VehiclePropValueBuilder.newBuilder(VehicleProperty.HW_KEY_INPUT) 817 .addIntValue(down ? 0 : 1, keyCode, vehicleDisplayType) 818 .build()); 819 } 820 injectRotaryEvent(int displayTarget, int numClicks)821 private void injectRotaryEvent(int displayTarget, int numClicks) { 822 VehiclePropValueBuilder builder = VehiclePropValueBuilder.newBuilder( 823 VehicleProperty.HW_ROTARY_INPUT) 824 .addIntValue(0 /* navigation oly for now */, numClicks, displayTarget); 825 for (int i = 0; i < numClicks - 1; i++) { 826 builder.addIntValue(0); 827 } 828 getMockedVehicleHal().injectEvent(builder.build()); 829 } 830 injectCustomInputEvent(int inputCode, int targetDisplayType, int repeatCounter)831 private void injectCustomInputEvent(int inputCode, int targetDisplayType, int repeatCounter) { 832 VehiclePropValueBuilder builder = VehiclePropValueBuilder.newBuilder( 833 VehicleProperty.HW_CUSTOM_INPUT) 834 .addIntValue(inputCode).addIntValue(targetDisplayType).addIntValue(repeatCounter); 835 getMockedVehicleHal().injectEvent(builder.build()); 836 } 837 } 838