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