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