• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.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.junit.Assert.assertThrows;
26 
27 import android.annotation.NonNull;
28 import android.car.Car;
29 import android.car.CarOccupantZoneManager;
30 import android.car.input.CarInputManager;
31 import android.car.input.CustomInputEvent;
32 import android.car.input.RotaryEvent;
33 import android.hardware.automotive.vehicle.RotaryInputType;
34 import android.hardware.automotive.vehicle.VehicleDisplay;
35 import android.hardware.automotive.vehicle.VehicleProperty;
36 import android.os.SystemClock;
37 import android.util.Log;
38 import android.util.Pair;
39 import android.view.KeyEvent;
40 
41 import androidx.test.ext.junit.runners.AndroidJUnit4;
42 import androidx.test.filters.MediumTest;
43 
44 import com.android.car.CarServiceUtils;
45 import com.android.car.MockedCarTestBase;
46 import com.android.car.hal.test.AidlVehiclePropValueBuilder;
47 import com.android.internal.annotations.GuardedBy;
48 
49 import org.junit.Test;
50 import org.junit.runner.RunWith;
51 
52 import java.util.Arrays;
53 import java.util.Collections;
54 import java.util.LinkedList;
55 import java.util.List;
56 import java.util.concurrent.ExecutorService;
57 import java.util.concurrent.Executors;
58 import java.util.concurrent.Semaphore;
59 import java.util.concurrent.TimeUnit;
60 
61 @RunWith(AndroidJUnit4.class)
62 @MediumTest
63 public final class CarInputManagerTest extends MockedCarTestBase {
64     private static final String TAG = CarInputManagerTest.class.getSimpleName();
65 
66     private static final int INVALID_DISPLAY_TYPE = -1;
67     private static final int INVALID_INPUT_TYPE = -1;
68 
69     private CarInputManager mCarInputManager;
70 
71     private static final class CaptureCallback implements CarInputManager.CarInputCaptureCallback {
72 
73         private static final long EVENT_WAIT_TIME = 5_000;
74 
75         private final Object mLock = new Object();
76 
77         private final String mName;
78 
CaptureCallback(String name)79         private CaptureCallback(String name) {
80             mName = name;
81         }
82 
83         // Stores passed events. Last one in front
84         @GuardedBy("mLock")
85         private final LinkedList<Pair<Integer, List<KeyEvent>>> mKeyEvents = new LinkedList<>();
86 
87         // Stores passed events. Last one in front
88         @GuardedBy("mLock")
89         private final LinkedList<Pair<Integer, List<RotaryEvent>>> mRotaryEvents =
90                 new LinkedList<>();
91 
92         // Stores passed events. Last one in front
93         @GuardedBy("mLock")
94         private final LinkedList<Pair<Integer, List<CustomInputEvent>>> mCustomInputEvents =
95                 new LinkedList<>();
96 
97         // Stores passed state changes. Last one in front
98         @GuardedBy("mLock")
99         private final LinkedList<Pair<Integer, int[]>> mStateChanges = new LinkedList<>();
100 
101         private final Semaphore mKeyEventWait = new Semaphore(0);
102         private final Semaphore mRotaryEventWait = new Semaphore(0);
103         private final Semaphore mStateChangeWait = new Semaphore(0);
104         private final Semaphore mCustomInputEventWait = new Semaphore(0);
105 
106         @Override
onKeyEvents(@isplayTypeEnum int targetDisplayType, @NonNull List<KeyEvent> keyEvents)107         public void onKeyEvents(@DisplayTypeEnum int targetDisplayType,
108                 @NonNull List<KeyEvent> keyEvents) {
109             Log.i(TAG, "onKeyEvents event:" + keyEvents.get(0) + " this:" + this);
110             synchronized (mLock) {
111                 mKeyEvents.addFirst(new Pair<>(targetDisplayType, keyEvents));
112             }
113             mKeyEventWait.release();
114         }
115 
116         @Override
onRotaryEvents(@isplayTypeEnum int targetDisplayType, @NonNull List<RotaryEvent> events)117         public void onRotaryEvents(@DisplayTypeEnum int targetDisplayType,
118                 @NonNull List<RotaryEvent> events) {
119             Log.i(TAG, "onRotaryEvents event:" + events.get(0) + " this:" + this);
120             synchronized (mLock) {
121                 mRotaryEvents.addFirst(new Pair<>(targetDisplayType, events));
122             }
123             mRotaryEventWait.release();
124         }
125 
126         @Override
onCustomInputEvents(@isplayTypeEnum int targetDisplayType, @NonNull List<CustomInputEvent> events)127         public void onCustomInputEvents(@DisplayTypeEnum int targetDisplayType,
128                 @NonNull List<CustomInputEvent> events) {
129             Log.i(TAG, "onCustomInputEvents event:" + events.get(0) + " this:" + this);
130             synchronized (mLock) {
131                 mCustomInputEvents.addFirst(new Pair<>(targetDisplayType, events));
132             }
133             mCustomInputEventWait.release();
134         }
135 
136         @Override
onCaptureStateChanged(@isplayTypeEnum int targetDisplayType, @NonNull @CarInputManager.InputTypeEnum int[] activeInputTypes)137         public void onCaptureStateChanged(@DisplayTypeEnum int targetDisplayType,
138                 @NonNull @CarInputManager.InputTypeEnum int[] activeInputTypes) {
139             Log.i(TAG, "onCaptureStateChanged types:" + Arrays.toString(activeInputTypes)
140                     + " this:" + this);
141             synchronized (mLock) {
142                 mStateChanges.addFirst(new Pair<>(targetDisplayType, activeInputTypes));
143             }
144             mStateChangeWait.release();
145         }
146 
resetAllEventsWaiting()147         private void resetAllEventsWaiting() {
148             mStateChangeWait.drainPermits();
149             mKeyEventWait.drainPermits();
150             mRotaryEventWait.drainPermits();
151         }
152 
waitForStateChange()153         private void waitForStateChange() throws Exception {
154             assertWithMessage("Failed to acquire semaphore in %s ms", EVENT_WAIT_TIME).that(
155                     mStateChangeWait.tryAcquire(EVENT_WAIT_TIME, TimeUnit.MILLISECONDS)).isTrue();
156         }
157 
waitForKeyEvent()158         private void waitForKeyEvent() throws Exception {
159             assertWithMessage("Failed to acquire semaphore in %s ms", EVENT_WAIT_TIME).that(
160                     mKeyEventWait.tryAcquire(EVENT_WAIT_TIME, TimeUnit.MILLISECONDS)).isTrue();
161         }
162 
waitForRotaryEvent()163         private void waitForRotaryEvent() throws Exception {
164             assertWithMessage("Failed to acquire semaphore in %s ms", EVENT_WAIT_TIME).that(
165                     mRotaryEventWait.tryAcquire(EVENT_WAIT_TIME, TimeUnit.MILLISECONDS)).isTrue();
166         }
167 
waitForCustomInputEvent()168         private void waitForCustomInputEvent() throws Exception {
169             assertWithMessage("Failed to acquire semaphore in %s ms", EVENT_WAIT_TIME).that(
170                     mCustomInputEventWait.tryAcquire(
171                             EVENT_WAIT_TIME, TimeUnit.MILLISECONDS)).isTrue();
172         }
173 
getkeyEvents()174         private LinkedList<Pair<Integer, List<KeyEvent>>> getkeyEvents() {
175             synchronized (mLock) {
176                 LinkedList<Pair<Integer, List<KeyEvent>>> r =
177                         new LinkedList<>(mKeyEvents);
178                 Log.i(TAG, "getKeyEvents size:" + r.size() + ",this:" + this);
179                 return r;
180             }
181         }
182 
getRotaryEvents()183         private LinkedList<Pair<Integer, List<RotaryEvent>>> getRotaryEvents() {
184             synchronized (mLock) {
185                 LinkedList<Pair<Integer, List<RotaryEvent>>> r =
186                         new LinkedList<>(mRotaryEvents);
187                 Log.i(TAG, "getRotaryEvents size:" + r.size() + ",this:" + this);
188                 return r;
189             }
190         }
191 
getStateChanges()192         private LinkedList<Pair<Integer, int[]>> getStateChanges() {
193             synchronized (mLock) {
194                 return new LinkedList<>(mStateChanges);
195             }
196         }
197 
getCustomInputEvents()198         private LinkedList<Pair<Integer, List<CustomInputEvent>>> getCustomInputEvents() {
199             synchronized (mLock) {
200                 LinkedList<Pair<Integer, List<CustomInputEvent>>> r =
201                         new LinkedList<>(mCustomInputEvents);
202                 Log.i(TAG, "getCustomInputEvents size:" + r.size() + ",this:" + this);
203                 return r;
204             }
205         }
206 
207         @Override
toString()208         public String toString() {
209             return "CaptureCallback{mName='" + mName + "'}";
210         }
211     }
212 
213     private final CaptureCallback mCallback0 = new CaptureCallback("callback0");
214     private final CaptureCallback mCallback1 = new CaptureCallback("callback1");
215     private final CaptureCallback mCallback2 = new CaptureCallback("callback2");
216 
217     @Override
configureMockedHal()218     protected void configureMockedHal() {
219         addAidlProperty(VehicleProperty.HW_KEY_INPUT,
220                 AidlVehiclePropValueBuilder.newBuilder(VehicleProperty.HW_KEY_INPUT)
221                         .addIntValues(0, 0, 0)
222                         .build());
223         addAidlProperty(VehicleProperty.HW_ROTARY_INPUT,
224                 AidlVehiclePropValueBuilder.newBuilder(VehicleProperty.HW_ROTARY_INPUT)
225                         .addIntValues(0, 1, 0)
226                         .build());
227         addAidlProperty(VehicleProperty.HW_CUSTOM_INPUT,
228                 AidlVehiclePropValueBuilder.newBuilder(VehicleProperty.HW_CUSTOM_INPUT)
229                         .addIntValues(0)
230                         .build());
231     }
232 
233     @Override
configureResourceOverrides(MockResources resources)234     protected void configureResourceOverrides(MockResources resources) {
235         super.configureResourceOverrides(resources);
236         resources.overrideResource(com.android.car.R.string.config_clusterHomeActivity,
237                 getTestContext().getPackageName() + "/" + CarInputManagerTest.class.getName());
238     }
239 
240     @Override
setUp()241     public void setUp() throws Exception {
242         super.setUp();
243         mCarInputManager = (CarInputManager) getCar().getCarManager(Car.CAR_INPUT_SERVICE);
244         assertThat(mCarInputManager).isNotNull();
245     }
246 
247     @Test
testInvalidArgs()248     public void testInvalidArgs() {
249         // Invalid display
250         assertThrows(IllegalArgumentException.class,
251                 () -> mCarInputManager.requestInputEventCapture(INVALID_DISPLAY_TYPE,
252                         new int[]{CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION}, 0, mCallback0));
253 
254         // Invalid input types
255         assertThrows(IllegalArgumentException.class,
256                 () -> mCarInputManager.requestInputEventCapture(
257                         CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
258                         new int[]{CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION, INVALID_INPUT_TYPE},
259                         0, mCallback0));
260         assertThrows(IllegalArgumentException.class,
261                 () -> mCarInputManager.requestInputEventCapture(
262                         CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
263                         new int[]{CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION},
264                         CarInputManager.CAPTURE_REQ_FLAGS_TAKE_ALL_EVENTS_FOR_DISPLAY, mCallback0));
265         assertThrows(IllegalArgumentException.class,
266                 () -> mCarInputManager.requestInputEventCapture(
267                         CarOccupantZoneManager.DISPLAY_TYPE_MAIN, new int[]{INVALID_INPUT_TYPE},
268                         0, mCallback0));
269         assertThrows(IllegalArgumentException.class,
270                 () -> mCarInputManager.requestInputEventCapture(
271                         CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
272                         new int[]{CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION, INVALID_INPUT_TYPE},
273                         0, mCallback0));
274     }
275 
createAnotherCarInputManager()276     private CarInputManager createAnotherCarInputManager() {
277         return (CarInputManager) createNewCar().getCarManager(Car.CAR_INPUT_SERVICE);
278     }
279 
280     @Test
testInjectKeyEvent_mainDisplay()281     public void testInjectKeyEvent_mainDisplay() throws Exception {
282         int r = mCarInputManager.requestInputEventCapture(
283                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
284                 new int[]{CarInputManager.INPUT_TYPE_ALL_INPUTS},
285                 CarInputManager.CAPTURE_REQ_FLAGS_TAKE_ALL_EVENTS_FOR_DISPLAY, mCallback0);
286         assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED);
287 
288         KeyEvent keyEvent = newKeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER);
289 
290         mCarInputManager.injectKeyEvent(keyEvent, CarOccupantZoneManager.DISPLAY_TYPE_MAIN);
291 
292         mCallback0.waitForKeyEvent();
293         assertThat(mCallback0.getkeyEvents()).containsExactly(
294                 new Pair<>(CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
295                         Collections.singletonList(keyEvent)));
296     }
297 
298     @Test
testInjectKeyEvent_instrumentClusterDisplay()299     public void testInjectKeyEvent_instrumentClusterDisplay() throws Exception {
300         int r = mCarInputManager.requestInputEventCapture(
301                 CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER,
302                 new int[]{CarInputManager.INPUT_TYPE_ALL_INPUTS},
303                 CarInputManager.CAPTURE_REQ_FLAGS_TAKE_ALL_EVENTS_FOR_DISPLAY, mCallback0);
304         assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED);
305 
306         KeyEvent keyEvent = newKeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER);
307 
308         mCarInputManager.injectKeyEvent(keyEvent,
309                 CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER);
310 
311         mCallback0.waitForKeyEvent();
312         assertThat(mCallback0.getkeyEvents()).containsExactly(
313                 new Pair<>(CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER,
314                         Collections.singletonList(keyEvent)));
315     }
316 
newKeyEvent(int action, int code)317     private static KeyEvent newKeyEvent(int action, int code) {
318         long currentTime = SystemClock.uptimeMillis();
319         return new KeyEvent(/* downTime= */ currentTime,
320                 /* eventTime= */ currentTime, action, code,
321                 /* repeat= */ 0);
322     }
323 
324     @Test
testFailWithFullCaptureHigherPriority()325     public void testFailWithFullCaptureHigherPriority() {
326         CarInputManager carInputManager0 = createAnotherCarInputManager();
327         int r = carInputManager0.requestInputEventCapture(
328                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
329                 new int[]{CarInputManager.INPUT_TYPE_ALL_INPUTS},
330                 CarInputManager.CAPTURE_REQ_FLAGS_TAKE_ALL_EVENTS_FOR_DISPLAY, mCallback0);
331         assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED);
332 
333         //TODO(b/151225008) test event
334 
335         r = mCarInputManager.requestInputEventCapture(
336                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
337                 new int[]{CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION}, 0, mCallback1);
338         assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_FAILED);
339 
340         carInputManager0.releaseInputEventCapture(CarOccupantZoneManager.DISPLAY_TYPE_MAIN);
341 
342         r = mCarInputManager.requestInputEventCapture(
343                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
344                 new int[]{CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION}, 0, mCallback1);
345         assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED);
346 
347         //TODO(b/151225008) test event
348     }
349 
350     @Test
testDelayedGrantWithFullCapture()351     public void testDelayedGrantWithFullCapture() throws Exception {
352         mCallback1.resetAllEventsWaiting();
353         CarInputManager carInputManager0 = createAnotherCarInputManager();
354         int r = carInputManager0.requestInputEventCapture(
355                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
356                 new int[]{CarInputManager.INPUT_TYPE_ALL_INPUTS},
357                 CarInputManager.CAPTURE_REQ_FLAGS_TAKE_ALL_EVENTS_FOR_DISPLAY, mCallback0);
358         assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED);
359 
360         injectKeyEvent(true, KeyEvent.KEYCODE_NAVIGATE_NEXT);
361         waitAndAssertLastKeyEvent(CarOccupantZoneManager.DISPLAY_TYPE_MAIN, true,
362                 KeyEvent.KEYCODE_NAVIGATE_NEXT, mCallback0);
363 
364         injectKeyEvent(true, KeyEvent.KEYCODE_DPAD_CENTER);
365         waitAndAssertLastKeyEvent(CarOccupantZoneManager.DISPLAY_TYPE_MAIN, true,
366                 KeyEvent.KEYCODE_DPAD_CENTER, mCallback0);
367 
368         int numClicks = 3;
369         injectRotaryNavigationEvent(VehicleDisplay.MAIN, numClicks);
370         waitAndAssertLastRotaryEvent(CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
371                 CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION, numClicks, mCallback0);
372 
373         r = mCarInputManager.requestInputEventCapture(
374                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
375                 new int[]{CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION},
376                 CarInputManager.CAPTURE_REQ_FLAGS_ALLOW_DELAYED_GRANT, mCallback1);
377         assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_DELAYED);
378 
379         injectRotaryNavigationEvent(VehicleDisplay.MAIN, numClicks);
380         waitForDispatchToMain();
381         assertNumberOfOnRotaryEvents(0, mCallback1);
382 
383         carInputManager0.releaseInputEventCapture(CarOccupantZoneManager.DISPLAY_TYPE_MAIN);
384 
385         // Now capture should be granted back
386         waitAndAssertLastStateChange(CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
387                 new int[]{
388                         CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION},
389                 mCallback1);
390         assertNoStateChange(mCallback0);
391 
392         injectRotaryNavigationEvent(VehicleDisplay.MAIN, numClicks);
393         waitAndAssertLastRotaryEvent(CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
394                 CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION, numClicks, mCallback1);
395     }
396 
397     @Test
testOneClientTransitionFromFullToNonFull()398     public void testOneClientTransitionFromFullToNonFull() throws Exception {
399         CarInputManager carInputManager0 = createAnotherCarInputManager();
400 
401         int r = carInputManager0.requestInputEventCapture(
402                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
403                 new int[]{CarInputManager.INPUT_TYPE_ALL_INPUTS},
404                 CarInputManager.CAPTURE_REQ_FLAGS_TAKE_ALL_EVENTS_FOR_DISPLAY, mCallback0);
405         assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED);
406 
407         r = mCarInputManager.requestInputEventCapture(
408                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
409                 new int[]{
410                         CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION,
411                         CarInputManager.INPUT_TYPE_NAVIGATE_KEYS},
412                 CarInputManager.CAPTURE_REQ_FLAGS_ALLOW_DELAYED_GRANT, mCallback1);
413         assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_DELAYED);
414 
415         r = carInputManager0.requestInputEventCapture(
416                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
417                 new int[]{
418                         CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION,
419                         CarInputManager.INPUT_TYPE_DPAD_KEYS}, 0, mCallback0);
420         assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED);
421 
422         waitForDispatchToMain();
423         waitAndAssertLastStateChange(CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
424                 new int[]{CarInputManager.INPUT_TYPE_NAVIGATE_KEYS},
425                 mCallback1);
426         assertNoStateChange(mCallback0);
427     }
428 
429     @Test
testSwitchFromFullCaptureToPerTypeCapture()430     public void testSwitchFromFullCaptureToPerTypeCapture() {
431         int r = mCarInputManager.requestInputEventCapture(
432                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
433                 new int[]{CarInputManager.INPUT_TYPE_ALL_INPUTS},
434                 CarInputManager.CAPTURE_REQ_FLAGS_TAKE_ALL_EVENTS_FOR_DISPLAY, mCallback0);
435         assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED);
436 
437         r = mCarInputManager.requestInputEventCapture(
438                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
439                 new int[]{CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION}, 0, mCallback1);
440         assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED);
441 
442         r = mCarInputManager.requestInputEventCapture(
443                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
444                 new int[]{CarInputManager.INPUT_TYPE_CUSTOM_INPUT_EVENT}, 0, mCallback2);
445         assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED);
446     }
447 
448     @Test
testIndependentTwoCaptures()449     public void testIndependentTwoCaptures() throws Exception {
450         int r = createAnotherCarInputManager().requestInputEventCapture(
451                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
452                 new int[]{CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION}, 0, mCallback0);
453         assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED);
454 
455         int numClicks = 3;
456         injectRotaryNavigationEvent(VehicleDisplay.MAIN, numClicks);
457         waitAndAssertLastRotaryEvent(CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
458                 CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION, numClicks, mCallback0);
459 
460         r = mCarInputManager.requestInputEventCapture(
461                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
462                 new int[]{CarInputManager.INPUT_TYPE_NAVIGATE_KEYS}, 0, mCallback1);
463         assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED);
464 
465         injectKeyEvent(true, KeyEvent.KEYCODE_NAVIGATE_NEXT);
466         waitAndAssertLastKeyEvent(CarOccupantZoneManager.DISPLAY_TYPE_MAIN, true,
467                 KeyEvent.KEYCODE_NAVIGATE_NEXT, mCallback1);
468     }
469 
470     @Test
testTwoClientsOverwrap()471     public void testTwoClientsOverwrap() throws Exception {
472         CarInputManager carInputManager0 = createAnotherCarInputManager();
473         CarInputManager carInputManager1 = createAnotherCarInputManager();
474 
475         mCallback0.resetAllEventsWaiting();
476         int r = carInputManager0.requestInputEventCapture(
477                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
478                 new int[]{
479                         CarInputManager.INPUT_TYPE_DPAD_KEYS,
480                         CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION}, 0, mCallback0);
481         assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED);
482 
483         injectKeyEvent(true, KeyEvent.KEYCODE_DPAD_CENTER);
484         waitAndAssertLastKeyEvent(CarOccupantZoneManager.DISPLAY_TYPE_MAIN, true,
485                 KeyEvent.KEYCODE_DPAD_CENTER, mCallback0);
486 
487         int numClicks = 3;
488         injectRotaryNavigationEvent(VehicleDisplay.MAIN, numClicks);
489         waitAndAssertLastRotaryEvent(CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
490                 CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION, numClicks, mCallback0);
491 
492         r = carInputManager1.requestInputEventCapture(
493                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
494                 new int[]{
495                         CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION,
496                         CarInputManager.INPUT_TYPE_NAVIGATE_KEYS}, 0, mCallback1);
497         assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED);
498 
499         waitAndAssertLastStateChange(CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
500                 new int[]{CarInputManager.INPUT_TYPE_DPAD_KEYS},
501                 mCallback0);
502         assertNoStateChange(mCallback1);
503 
504         injectKeyEvent(true, KeyEvent.KEYCODE_NAVIGATE_NEXT);
505         waitAndAssertLastKeyEvent(CarOccupantZoneManager.DISPLAY_TYPE_MAIN, true,
506                 KeyEvent.KEYCODE_NAVIGATE_NEXT, mCallback1);
507         assertNumberOfOnKeyEvents(1, mCallback0);
508 
509         injectKeyEvent(true, KeyEvent.KEYCODE_DPAD_CENTER);
510         waitAndAssertLastKeyEvent(CarOccupantZoneManager.DISPLAY_TYPE_MAIN, true,
511                 KeyEvent.KEYCODE_DPAD_CENTER, mCallback0);
512         assertNumberOfOnKeyEvents(2, mCallback0);
513 
514         injectRotaryNavigationEvent(VehicleDisplay.MAIN, numClicks);
515         waitAndAssertLastRotaryEvent(CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
516                 CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION, numClicks, mCallback1);
517         assertNumberOfOnRotaryEvents(1, mCallback0);
518 
519         mCallback0.resetAllEventsWaiting();
520         carInputManager1.releaseInputEventCapture(CarOccupantZoneManager.DISPLAY_TYPE_MAIN);
521 
522         waitAndAssertLastStateChange(CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
523                 new int[]{CarInputManager.INPUT_TYPE_DPAD_KEYS,
524                         CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION},
525                 mCallback0);
526         assertNoStateChange(mCallback1);
527 
528         injectRotaryNavigationEvent(VehicleDisplay.MAIN, numClicks);
529         waitAndAssertLastRotaryEvent(CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
530                 CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION, numClicks, mCallback0);
531     }
532 
533     @Test
testInteractionWithFullCapturer()534     public void testInteractionWithFullCapturer() throws Exception {
535         CarInputManager carInputManager0 = createAnotherCarInputManager();
536         CarInputManager carInputManager1 = createAnotherCarInputManager();
537 
538         // Request rotary and dpad input event capture for display main (mCallback0)
539         int r = carInputManager0.requestInputEventCapture(
540                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
541                 new int[]{
542                         CarInputManager.INPUT_TYPE_DPAD_KEYS,
543                         CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION}, 0, mCallback0);
544         assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED);
545 
546         // Request all input event capture for display main (mCallback1)
547         r = carInputManager1.requestInputEventCapture(
548                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
549                 new int[]{CarInputManager.INPUT_TYPE_ALL_INPUTS},
550                 CarInputManager.CAPTURE_REQ_FLAGS_TAKE_ALL_EVENTS_FOR_DISPLAY, mCallback1);
551         assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED);
552 
553         waitForDispatchToMain();
554         waitAndAssertLastStateChange(CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
555                 new int[0], mCallback0);
556         assertNoStateChange(mCallback1);
557 
558         // Release input event capture for main display
559         carInputManager1.releaseInputEventCapture(CarOccupantZoneManager.DISPLAY_TYPE_MAIN);
560         waitForDispatchToMain();
561         waitAndAssertLastStateChange(CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
562                 new int[]{CarInputManager.INPUT_TYPE_DPAD_KEYS,
563                         CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION},
564                 mCallback0);
565         assertNoStateChange(mCallback1);
566     }
567 
568     @Test
testFullCapturerAcceptsNotMappedKey()569     public void testFullCapturerAcceptsNotMappedKey() throws Exception {
570         int r = mCarInputManager.requestInputEventCapture(
571                 CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER,
572                 new int[]{CarInputManager.INPUT_TYPE_ALL_INPUTS},
573                 CarInputManager.CAPTURE_REQ_FLAGS_TAKE_ALL_EVENTS_FOR_DISPLAY, mCallback0);
574         assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED);
575 
576         injectKeyEvent(true, KeyEvent.KEYCODE_MENU, VehicleDisplay.INSTRUMENT_CLUSTER);
577         waitAndAssertLastKeyEvent(CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER, true,
578                 KeyEvent.KEYCODE_MENU, mCallback0);
579     }
580 
581     @Test
testSingleClientUpdates()582     public void testSingleClientUpdates() {
583         int r = mCarInputManager.requestInputEventCapture(
584                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
585                 new int[]{
586                         CarInputManager.INPUT_TYPE_DPAD_KEYS,
587                         CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION}, 0, mCallback0);
588         assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED);
589 
590         r = mCarInputManager.requestInputEventCapture(
591                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
592                 new int[]{
593                         CarInputManager.INPUT_TYPE_DPAD_KEYS,
594                         CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION}, 0, mCallback0);
595         assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED);
596 
597         waitForDispatchToMain();
598         assertNoStateChange(mCallback0);
599 
600         r = mCarInputManager.requestInputEventCapture(
601                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
602                 new int[]{
603                         CarInputManager.INPUT_TYPE_DPAD_KEYS,
604                         CarInputManager.INPUT_TYPE_NAVIGATE_KEYS}, 0, mCallback0);
605         assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED);
606 
607         waitForDispatchToMain();
608         assertNoStateChange(mCallback0);
609 
610         r = mCarInputManager.requestInputEventCapture(
611                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
612                 new int[]{CarInputManager.INPUT_TYPE_ALL_INPUTS},
613                 CarInputManager.CAPTURE_REQ_FLAGS_TAKE_ALL_EVENTS_FOR_DISPLAY, mCallback0);
614         assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED);
615 
616         waitForDispatchToMain();
617         assertNoStateChange(mCallback0);
618 
619         r = mCarInputManager.requestInputEventCapture(
620                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
621                 new int[]{CarInputManager.INPUT_TYPE_ALL_INPUTS},
622                 CarInputManager.CAPTURE_REQ_FLAGS_TAKE_ALL_EVENTS_FOR_DISPLAY, mCallback0);
623         assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED);
624 
625         waitForDispatchToMain();
626         assertNoStateChange(mCallback0);
627     }
628 
629     @Test
testInjectingRotaryEventAndExecutor()630     public void testInjectingRotaryEventAndExecutor() throws Exception {
631         // Arrange executors to process events
632         ExecutorService rotaryExecutor = Executors.newSingleThreadExecutor();
633         try {
634             // Arrange: register callback
635             CarInputManager carInputManager = createAnotherCarInputManager();
636             int r = carInputManager.requestInputEventCapture(
637                     CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
638                     new int[]{CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION}, 0, rotaryExecutor,
639                     mCallback0);
640             assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED);
641 
642             // Act: inject RotaryEvent
643             int numClicks = 3;
644             injectRotaryNavigationEvent(VehicleDisplay.MAIN, numClicks);
645 
646             // Assert: ensure RotaryEvent was delivered
647             waitAndAssertLastRotaryEvent(CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
648                     CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION, numClicks, mCallback0);
649         } finally {
650             rotaryExecutor.shutdown();
651         }
652     }
653 
654     @Test
testInjectingKeyEventAndExecutor()655     public void testInjectingKeyEventAndExecutor() throws Exception {
656         // Arrange executors to process events
657         ExecutorService keyEventExecutor = Executors.newSingleThreadExecutor();
658         try {
659             // Arrange: register callback
660             CarInputManager carInputManager = createAnotherCarInputManager();
661             int r = carInputManager.requestInputEventCapture(
662                     CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
663                     new int[]{CarInputManager.INPUT_TYPE_NAVIGATE_KEYS}, 0, keyEventExecutor,
664                     mCallback0);
665             assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED);
666 
667             // Act: inject KeyEvent
668             injectKeyEvent(true, KeyEvent.KEYCODE_NAVIGATE_NEXT);
669 
670             // Assert: ensure KeyEvent was delivered
671             waitAndAssertLastKeyEvent(CarOccupantZoneManager.DISPLAY_TYPE_MAIN, true,
672                     KeyEvent.KEYCODE_NAVIGATE_NEXT, mCallback0);
673         } finally {
674             keyEventExecutor.shutdown();
675         }
676     }
677 
678     @Test
testInjectingVoiceAssistKeyEventAndExecutor()679     public void testInjectingVoiceAssistKeyEventAndExecutor() throws Exception {
680         // Arrange executors to process events
681         ExecutorService keyEventExecutor = Executors.newSingleThreadExecutor();
682         try {
683             // Arrange: register callback
684             CarInputManager carInputManager = createAnotherCarInputManager();
685             int r = carInputManager.requestInputEventCapture(
686                     CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
687                     new int[]{CarInputManager.INPUT_TYPE_SYSTEM_NAVIGATE_KEYS}, 0, keyEventExecutor,
688                     mCallback0);
689             assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED);
690 
691             // Act: inject KeyEvent
692             injectKeyEvent(false, KeyEvent.KEYCODE_VOICE_ASSIST);
693 
694             // Assert: ensure KeyEvent was delivered
695             waitAndAssertLastKeyEvent(CarOccupantZoneManager.DISPLAY_TYPE_MAIN, false,
696                     KeyEvent.KEYCODE_VOICE_ASSIST, mCallback0);
697         } finally {
698             keyEventExecutor.shutdown();
699         }
700     }
701 
702 
703     @Test
testInjectingCustomInputEventAndExecutor()704     public void testInjectingCustomInputEventAndExecutor() throws Exception {
705         // Arrange executors to process events
706         ExecutorService customInputEventExecutor = Executors.newSingleThreadExecutor();
707         try {
708             // Arrange: register callback
709             CarInputManager carInputManager = createAnotherCarInputManager();
710             int r = carInputManager.requestInputEventCapture(
711                     CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
712                     new int[]{CarInputManager.INPUT_TYPE_CUSTOM_INPUT_EVENT}, 0,
713                     customInputEventExecutor, mCallback0);
714             assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED);
715 
716             // Act: inject CustomInputEvent
717             int repeatedCounter = 1;
718             injectCustomInputEvent(CUSTOM_EVENT_F1, VehicleDisplay.MAIN,
719                     /* repeatCounter= */ repeatedCounter);
720 
721             // Assert: ensure CustomInputEvent was delivered
722             waitAndAssertLastCustomInputEvent(CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
723                     CUSTOM_EVENT_F1,
724                     repeatedCounter, mCallback0);
725         } finally {
726             customInputEventExecutor.shutdown();
727         }
728     }
729 
730     @Test
testRotaryVolumeTypeIsNotSupportedYet()731     public void testRotaryVolumeTypeIsNotSupportedYet() {
732         assertThrows(IllegalArgumentException.class,
733                 () -> mCarInputManager.requestInputEventCapture(
734                         CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
735                         new int[]{CarInputManager.INPUT_TYPE_ROTARY_VOLUME}, 0, mCallback0));
736     }
737 
738     @Test
testCallbacksForDifferentDisplays_keyInputEvents_mainDisplay()739     public void testCallbacksForDifferentDisplays_keyInputEvents_mainDisplay() throws Exception {
740         registerCallbackForAllInputs(CarOccupantZoneManager.DISPLAY_TYPE_MAIN, mCallback0);
741         registerCallbackForAllInputs(CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER,
742                 mCallback1);
743 
744         sendAndAssertKeyEvent(KeyEvent.KEYCODE_HOME, CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
745                 mCallback0);
746         assertNoKeyEventSent(mCallback1);
747     }
748 
749     @Test
testCallbacksForDifferentDisplays_keyInputEvents_clusterDisplay()750     public void testCallbacksForDifferentDisplays_keyInputEvents_clusterDisplay() throws Exception {
751         registerCallbackForAllInputs(CarOccupantZoneManager.DISPLAY_TYPE_MAIN, mCallback0);
752         registerCallbackForAllInputs(CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER,
753                 mCallback1);
754 
755         sendAndAssertKeyEvent(KeyEvent.KEYCODE_HOME,
756                 CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER,
757                 mCallback1);
758         assertNoKeyEventSent(mCallback0);
759     }
760 
761     @Test
testCallbacksForDifferentDisplays_customInputEvents_mainDisplay()762     public void testCallbacksForDifferentDisplays_customInputEvents_mainDisplay() throws Exception {
763         registerCallbackForAllInputs(CarOccupantZoneManager.DISPLAY_TYPE_MAIN, mCallback0);
764         registerCallbackForAllInputs(CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER,
765                 mCallback1);
766 
767         sendAndAssertCustomInputEvent(CUSTOM_EVENT_F1,
768                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN, /* repeatedCounter= */ 1,
769                 mCallback0);
770         assertNoCustomInputEventSent(mCallback1);
771     }
772 
773     @Test
testCallbacksForDifferentDisplays_customInputEvents_clusterDisplay()774     public void testCallbacksForDifferentDisplays_customInputEvents_clusterDisplay()
775             throws Exception {
776         registerCallbackForAllInputs(CarOccupantZoneManager.DISPLAY_TYPE_MAIN, mCallback0);
777         registerCallbackForAllInputs(CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER,
778                 mCallback1);
779 
780         sendAndAssertCustomInputEvent(CUSTOM_EVENT_F1,
781                 CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER, /* repeatedCounter= */ 1,
782                 mCallback1);
783         assertNoCustomInputEventSent(mCallback0);
784     }
785 
786     @Test
testCallbacksForDifferentDisplays_rotaryEvents_mainDisplay()787     public void testCallbacksForDifferentDisplays_rotaryEvents_mainDisplay() throws Exception {
788         registerCallbackForAllInputs(CarOccupantZoneManager.DISPLAY_TYPE_MAIN, mCallback0);
789         registerCallbackForAllInputs(CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER,
790                 mCallback1);
791 
792         sendAndAssertRotaryNavigationEvent(
793                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN, /* numClicks= */ 1,
794                 mCallback0);
795         assertNoRotaryEventSent(mCallback1);
796     }
797 
798     @Test
testCallbacksForDifferentDisplays_rotaryEvents_clusterDisplay()799     public void testCallbacksForDifferentDisplays_rotaryEvents_clusterDisplay() throws Exception {
800         registerCallbackForAllInputs(CarOccupantZoneManager.DISPLAY_TYPE_MAIN, mCallback0);
801         registerCallbackForAllInputs(CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER,
802                 mCallback1);
803 
804         sendAndAssertRotaryNavigationEvent(
805                 CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER, /* numClicks= */ 1,
806                 mCallback1);
807         assertNoRotaryEventSent(mCallback0);
808     }
809 
registerCallbackForAllInputs(int displayType, CaptureCallback callback)810     private void registerCallbackForAllInputs(int displayType, CaptureCallback callback) {
811         int respMain = mCarInputManager.requestInputEventCapture(
812                 displayType,
813                 new int[]{CarInputManager.INPUT_TYPE_ALL_INPUTS},
814                 CarInputManager.CAPTURE_REQ_FLAGS_TAKE_ALL_EVENTS_FOR_DISPLAY, callback);
815         assertThat(respMain).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED);
816     }
817 
818     @Test
testInputTypeSystemNavigateKeys()819     public void testInputTypeSystemNavigateKeys() throws Exception {
820         int r = mCarInputManager.requestInputEventCapture(
821                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
822                 new int[]{CarInputManager.INPUT_TYPE_SYSTEM_NAVIGATE_KEYS}, 0, mCallback0);
823         assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED);
824 
825         sendAndAssertKeyEvent(KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN,
826                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN, mCallback0);
827         sendAndAssertKeyEvent(KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP,
828                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN, mCallback0);
829         sendAndAssertKeyEvent(KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT,
830                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN, mCallback0);
831         sendAndAssertKeyEvent(KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT,
832                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN, mCallback0);
833     }
834 
sendAndAssertKeyEvent(int keyCode, int displayType, CaptureCallback callback)835     private void sendAndAssertKeyEvent(int keyCode, int displayType, CaptureCallback callback)
836             throws Exception {
837         KeyEvent keyEvent = newKeyEvent(KeyEvent.ACTION_DOWN, keyCode);
838 
839         mCarInputManager.injectKeyEvent(keyEvent, displayType);
840 
841         callback.waitForKeyEvent();
842         assertThat(callback.getkeyEvents()).contains(
843                 new Pair<>(displayType,
844                         Collections.singletonList(keyEvent)));
845     }
846 
sendAndAssertCustomInputEvent(int inputCode, int displayType, int repeatedCounter, CaptureCallback callback)847     private void sendAndAssertCustomInputEvent(int inputCode, int displayType, int repeatedCounter,
848             CaptureCallback callback)
849             throws Exception {
850         injectCustomInputEvent(inputCode,
851                 convertToVehicleDisplay(displayType), repeatedCounter);
852 
853         callback.waitForCustomInputEvent();
854         assertThat(callback.getCustomInputEvents()).hasSize(1);
855         Pair<Integer, List<CustomInputEvent>> customInputEvents =
856                 callback.getCustomInputEvents().get(0);
857         assertThat(customInputEvents.second).hasSize(1);
858         CustomInputEvent customInputEvent = customInputEvents.second.get(0);
859         assertThat(customInputEvent.getInputCode()).isEqualTo(inputCode);
860         assertThat(customInputEvent.getTargetDisplayType()).isEqualTo(displayType);
861         assertThat(customInputEvent.getRepeatCounter()).isEqualTo(repeatedCounter);
862     }
863 
864     /**
865      * Utility method to convert CarOccupantZoneManager display type to Vehicle HAL display type
866      */
convertToVehicleDisplay(int vehicleDisplayType)867     private static int convertToVehicleDisplay(int vehicleDisplayType) {
868         switch (vehicleDisplayType) {
869             case CarOccupantZoneManager.DISPLAY_TYPE_MAIN:
870                 return VehicleDisplay.MAIN;
871             case CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER:
872                 return VehicleDisplay.INSTRUMENT_CLUSTER;
873             default:
874                 throw new IllegalArgumentException(
875                         "CarOccupantZone display type {" + vehicleDisplayType
876                                 + "} has no equivalent in VehicleDisplay display type");
877         }
878     }
879 
sendAndAssertRotaryNavigationEvent(int displayType, int numClicks, CaptureCallback callback)880     private void sendAndAssertRotaryNavigationEvent(int displayType, int numClicks,
881             CaptureCallback callback)
882             throws Exception {
883         injectRotaryNavigationEvent(convertToVehicleDisplay(displayType),
884                 numClicks);
885 
886         callback.waitForRotaryEvent();
887         assertThat(callback.getRotaryEvents()).hasSize(1);
888         Pair<Integer, List<RotaryEvent>> capturedEvents = callback.getRotaryEvents().get(0);
889         assertThat(capturedEvents.second).hasSize(1);
890         RotaryEvent rotaryEvent = capturedEvents.second.get(0);
891         assertThat(rotaryEvent.getNumberOfClicks()).isEqualTo(numClicks);
892     }
893 
894     /**
895      * Events dispatched to main, so this should guarantee that all event dispatched are completed.
896      */
waitForDispatchToMain()897     private void waitForDispatchToMain() {
898         // Needs to be invoked twice as it is dispatched to main inside car service once, and it is
899         // dispatched to main inside CarInputManager once.
900         CarServiceUtils.runOnMainSync(() -> {});
901         CarServiceUtils.runOnMainSync(() -> {});
902     }
903 
waitAndAssertLastKeyEvent(int displayTarget, boolean down, int keyCode, CaptureCallback callback)904     private void waitAndAssertLastKeyEvent(int displayTarget, boolean down, int keyCode,
905             CaptureCallback callback) throws Exception {
906         // Wait for key event first.
907         callback.waitForKeyEvent();
908 
909         LinkedList<Pair<Integer, List<KeyEvent>>> events = callback.getkeyEvents();
910         assertThat(events).isNotEmpty();
911         Pair<Integer, List<KeyEvent>> lastEvent = events.getFirst();
912         assertThat(lastEvent.first).isEqualTo(displayTarget);
913         assertThat(lastEvent.second).hasSize(1);
914         KeyEvent keyEvent = lastEvent.second.get(0);
915         assertThat(keyEvent.getAction()).isEqualTo(
916                 down ? KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP);
917         assertThat(keyEvent.getKeyCode()).isEqualTo(keyCode);
918     }
919 
assertNumberOfOnKeyEvents(int expectedNumber, CaptureCallback callback)920     private void assertNumberOfOnKeyEvents(int expectedNumber, CaptureCallback callback) {
921         LinkedList<Pair<Integer, List<KeyEvent>>> events = callback.getkeyEvents();
922         assertThat(events).hasSize(expectedNumber);
923     }
924 
waitAndAssertLastRotaryEvent(int displayTarget, int rotaryType, int numberOfClicks, CaptureCallback callback)925     private void waitAndAssertLastRotaryEvent(int displayTarget, int rotaryType, int numberOfClicks,
926             CaptureCallback callback) throws Exception {
927         // Wait for rotary event first.
928         callback.waitForRotaryEvent();
929 
930         LinkedList<Pair<Integer, List<RotaryEvent>>> rotaryEvents = callback.getRotaryEvents();
931         assertThat(rotaryEvents).isNotEmpty();
932         Pair<Integer, List<RotaryEvent>> lastEvent = rotaryEvents.getFirst();
933         assertThat(lastEvent.first).isEqualTo(displayTarget);
934         assertThat(lastEvent.second).hasSize(1);
935         RotaryEvent rotaryEvent = lastEvent.second.get(0);
936         assertThat(rotaryEvent.getInputType()).isEqualTo(rotaryType);
937         assertThat(rotaryEvent.getNumberOfClicks()).isEqualTo(numberOfClicks);
938         // TODO(b/151225008) Test timestamp
939     }
940 
assertNumberOfOnRotaryEvents(int expectedNumber, CaptureCallback callback)941     private void assertNumberOfOnRotaryEvents(int expectedNumber, CaptureCallback callback) {
942         LinkedList<Pair<Integer, List<RotaryEvent>>> rotaryEvents = callback.getRotaryEvents();
943         assertThat(rotaryEvents).hasSize(expectedNumber);
944     }
945 
waitAndAssertLastStateChange(int expectedTargetDisplayTarget, int[] expectedInputTypes, CaptureCallback callback)946     private void waitAndAssertLastStateChange(int expectedTargetDisplayTarget,
947             int[] expectedInputTypes, CaptureCallback callback) throws Exception {
948         // Wait for state change event first.
949         callback.waitForStateChange();
950 
951         LinkedList<Pair<Integer, int[]>> changes = callback.getStateChanges();
952         assertThat(changes).isNotEmpty();
953         Pair<Integer, int[]> lastChange = changes.getFirst();
954         assertStateChange(expectedTargetDisplayTarget, expectedInputTypes, lastChange);
955     }
956 
assertNoStateChange(CaptureCallback callback)957     private void assertNoStateChange(CaptureCallback callback) {
958         assertThat(callback.getStateChanges()).isEmpty();
959     }
960 
assertNoKeyEventSent(CaptureCallback callback)961     private void assertNoKeyEventSent(CaptureCallback callback) {
962         assertThat(callback.getkeyEvents()).isEmpty();
963     }
964 
assertNoCustomInputEventSent(CaptureCallback callback)965     private void assertNoCustomInputEventSent(CaptureCallback callback) {
966         assertThat(callback.getCustomInputEvents()).isEmpty();
967     }
968 
assertNoRotaryEventSent(CaptureCallback callback)969     private void assertNoRotaryEventSent(CaptureCallback callback) {
970         assertThat(callback.getRotaryEvents()).isEmpty();
971     }
972 
assertStateChange(int expectedTargetDisplayTarget, int[] expectedInputTypes, Pair<Integer, int[]> actual)973     private void assertStateChange(int expectedTargetDisplayTarget, int[] expectedInputTypes,
974             Pair<Integer, int[]> actual) {
975         Arrays.sort(expectedInputTypes);
976         assertThat(actual.first).isEqualTo(expectedTargetDisplayTarget);
977         assertThat(actual.second).isEqualTo(expectedInputTypes);
978     }
979 
waitAndAssertLastCustomInputEvent(int expectedDisplayType, int expectedCustomEventFunction, int expectedRepeatedCounter, CaptureCallback callback)980     private void waitAndAssertLastCustomInputEvent(int expectedDisplayType,
981             int expectedCustomEventFunction, int expectedRepeatedCounter,
982             CaptureCallback callback) throws Exception {
983         // Wait for custom input event first.
984         callback.waitForCustomInputEvent();
985 
986         LinkedList<Pair<Integer, List<CustomInputEvent>>> events = callback.getCustomInputEvents();
987         assertThat(events).isNotEmpty();
988 
989         Pair<Integer, List<CustomInputEvent>> lastEvent = events.getFirst();
990         assertThat(lastEvent.first).isEqualTo(expectedDisplayType);
991         assertThat(lastEvent.second).hasSize(1);
992 
993         CustomInputEvent event = lastEvent.second.get(0);
994         assertThat(event.getInputCode()).isEqualTo(expectedCustomEventFunction);
995         assertThat(event.getRepeatCounter()).isEqualTo(expectedRepeatedCounter);
996         assertThat(event.getTargetDisplayType()).isEqualTo(expectedDisplayType);
997     }
998 
injectKeyEvent(boolean down, int keyCode)999     private void injectKeyEvent(boolean down, int keyCode) {
1000         injectKeyEvent(down, keyCode, VehicleDisplay.MAIN);
1001     }
1002 
injectKeyEvent(boolean down, int keyCode, int vehicleDisplayType)1003     private void injectKeyEvent(boolean down, int keyCode, int vehicleDisplayType) {
1004         getAidlMockedVehicleHal().injectEvent(
1005                 AidlVehiclePropValueBuilder.newBuilder(VehicleProperty.HW_KEY_INPUT)
1006                         .addIntValues(down ? 0 : 1, keyCode, vehicleDisplayType)
1007                         .build());
1008     }
1009 
injectRotaryNavigationEvent(int displayTarget, int numClicks)1010     private void injectRotaryNavigationEvent(int displayTarget, int numClicks) {
1011         AidlVehiclePropValueBuilder builder = AidlVehiclePropValueBuilder.newBuilder(
1012                 VehicleProperty.HW_ROTARY_INPUT)
1013                 .addIntValues(RotaryInputType.ROTARY_INPUT_TYPE_SYSTEM_NAVIGATION, numClicks,
1014                         displayTarget);
1015         for (int i = 0; i < numClicks - 1; i++) {
1016             builder.addIntValues(0);
1017         }
1018         getAidlMockedVehicleHal().injectEvent(builder.build());
1019     }
1020 
injectCustomInputEvent(int inputCode, int targetDisplayType, int repeatCounter)1021     private void injectCustomInputEvent(int inputCode, int targetDisplayType, int repeatCounter) {
1022         AidlVehiclePropValueBuilder builder = AidlVehiclePropValueBuilder.newBuilder(
1023                 VehicleProperty.HW_CUSTOM_INPUT)
1024                 .addIntValues(inputCode).addIntValues(targetDisplayType)
1025                 .addIntValues(repeatCounter);
1026         getAidlMockedVehicleHal().injectEvent(builder.build());
1027     }
1028 }
1029