1 /*
2  * Copyright 2015 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 android.hardware.input.cts.tests;
18 
19 import static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertNotEquals;
21 import static org.junit.Assert.assertTrue;
22 import static org.junit.Assert.fail;
23 
24 import android.app.Instrumentation;
25 import android.hardware.input.cts.InputCallback;
26 import android.hardware.input.cts.InputCtsActivity;
27 import android.os.Bundle;
28 import android.util.Log;
29 import android.view.InputDevice;
30 import android.view.InputEvent;
31 import android.view.KeyEvent;
32 import android.view.MotionEvent;
33 import android.view.View;
34 
35 import androidx.annotation.NonNull;
36 import androidx.annotation.Nullable;
37 import androidx.test.core.app.ActivityScenario;
38 import androidx.test.platform.app.InstrumentationRegistry;
39 
40 import com.android.compatibility.common.util.PollingCheck;
41 import com.android.cts.input.DebugInputRule;
42 
43 import org.junit.After;
44 import org.junit.Before;
45 
46 import java.util.ArrayList;
47 import java.util.Arrays;
48 import java.util.HashSet;
49 import java.util.List;
50 import java.util.Set;
51 import java.util.concurrent.BlockingQueue;
52 import java.util.concurrent.CountDownLatch;
53 import java.util.concurrent.LinkedBlockingQueue;
54 import java.util.concurrent.TimeUnit;
55 
56 public abstract class InputTestCase {
57     private static final String TAG = "InputTestCase";
58     private static final float TOLERANCE = 0.005f;
59     private static final int NUM_MAX_ATTEMPTS_TO_RECEIVE_SINGLE_EVENT = 5;
60 
61     // Ignore comparing input values for these axes. This is used to prevent breakages caused by
62     // OEMs using custom key layouts to remap GAS/BRAKE to RTRIGGER/LTRIGGER (for example,
63     // b/197062720).
64     private static final Set<Integer> IGNORE_AXES = new HashSet<>(Arrays.asList(
65             MotionEvent.AXIS_LTRIGGER, MotionEvent.AXIS_RTRIGGER,
66             MotionEvent.AXIS_GAS, MotionEvent.AXIS_BRAKE));
67 
68     private final BlockingQueue<InputEvent> mEvents;
69     protected final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
70 
71     private final InputListener mInputListener;
72     View mDecorView;
73 
74     // Stores the name of the currently running test
75     protected String mCurrentTestCase;
76 
77     // State used for motion events
78     private int mLastButtonState;
79 
80     protected InputCtsActivity mTestActivity;
81 
InputTestCase()82     InputTestCase() {
83         mEvents = new LinkedBlockingQueue<>();
84         mInputListener = new InputListener();
85     }
86 
87     private ActivityScenario<InputCtsActivity> mActivityRule;
88 
89     @Before
setUp()90     public void setUp() throws Exception {
91         onBeforeLaunchActivity();
92         mActivityRule = ActivityScenario.launch(InputCtsActivity.class, getActivityOptions())
93                 .onActivity(activity -> mTestActivity = activity);
94         mTestActivity.clearUnhandleKeyCode();
95         mTestActivity.setInputCallback(mInputListener);
96         mDecorView = mTestActivity.getWindow().getDecorView();
97 
98         onSetUp();
99         PollingCheck.waitFor(mTestActivity::hasWindowFocus);
100         assertTrue(mCurrentTestCase + ": Activity window must have focus",
101                 mTestActivity.hasWindowFocus());
102 
103         mEvents.clear();
104     }
105 
106     @After
tearDown()107     public void tearDown() throws Exception {
108         onTearDown();
109         if (mActivityRule != null) {
110             mActivityRule.close();
111         }
112     }
113 
114     /** Optional setup logic performed before the test activity is launched. */
onBeforeLaunchActivity()115     void onBeforeLaunchActivity() {}
116 
onSetUp()117     abstract void onSetUp();
118 
onTearDown()119     abstract void onTearDown();
120 
121     /**
122      * Get the activity options to launch the activity with.
123      * @return the activity options or null.
124      */
getActivityOptions()125     @Nullable Bundle getActivityOptions() {
126         return null;
127     }
128 
129     /**
130      * Asserts that the application received a {@link KeyEvent} with the given metadata.
131      *
132      * If the expected {@link KeyEvent} is not received within a reasonable number of attempts, then
133      * this will throw an {@link AssertionError}.
134      *
135      * Only action, source, keyCode and metaState are being compared.
136      */
assertReceivedKeyEvent(@onNull KeyEvent expectedKeyEvent)137     private void assertReceivedKeyEvent(@NonNull KeyEvent expectedKeyEvent) {
138         KeyEvent receivedKeyEvent = waitForKey();
139         if (receivedKeyEvent == null) {
140             failWithMessage("Did not receive " + expectedKeyEvent);
141         }
142         assertEquals(mCurrentTestCase + " (action)",
143                 expectedKeyEvent.getAction(), receivedKeyEvent.getAction());
144         assertSource(mCurrentTestCase, expectedKeyEvent, receivedKeyEvent);
145         assertEquals(mCurrentTestCase + " (keycode) expected: "
146                 + KeyEvent.keyCodeToString(expectedKeyEvent.getKeyCode()) + " received: "
147                 + KeyEvent.keyCodeToString(receivedKeyEvent.getKeyCode()),
148                 expectedKeyEvent.getKeyCode(), receivedKeyEvent.getKeyCode());
149         assertMetaState(mCurrentTestCase, expectedKeyEvent.getMetaState(),
150                 receivedKeyEvent.getMetaState());
151     }
152 
153     /**
154      * Asserts that the application received a {@link MotionEvent} with the given metadata.
155      *
156      * If the expected {@link MotionEvent} is not received within a reasonable number of attempts,
157      * then this will throw an {@link AssertionError}.
158      *
159      * Only action, source, keyCode and metaState are being compared.
160      */
assertReceivedMotionEvent(@onNull MotionEvent expectedEvent)161     private void assertReceivedMotionEvent(@NonNull MotionEvent expectedEvent) {
162         MotionEvent event = waitForMotion();
163         /*
164          If the test fails here, one thing to try is to forcefully add a delay after the device
165          added callback has been received, but before any hid data has been written to the device.
166          We already wait for all of the proper callbacks here and in other places of the stack, but
167          it appears that the device sometimes is still not ready to receive hid data. If any data
168          gets written to the device in that state, it will disappear,
169          and no events will be generated.
170           */
171 
172         if (event == null) {
173             failWithMessage("Did not receive " + expectedEvent);
174         }
175         if (event.getHistorySize() > 0) {
176             failWithMessage("expected each MotionEvent to only have a single entry");
177         }
178         assertEquals(mCurrentTestCase + " (action) expected: "
179                 + MotionEvent.actionToString(expectedEvent.getAction()) + " received: " + event,
180                 expectedEvent.getAction(), event.getAction());
181         assertSource(mCurrentTestCase, expectedEvent, event);
182         assertEquals(mCurrentTestCase + " (button state)",
183                 expectedEvent.getButtonState(), event.getButtonState());
184         if (event.getActionMasked() == MotionEvent.ACTION_BUTTON_PRESS
185                 || event.getActionMasked() == MotionEvent.ACTION_BUTTON_RELEASE) {
186             // Only checking getActionButton() for ACTION_BUTTON_PRESS or ACTION_BUTTON_RELEASE
187             // because for actions other than ACTION_BUTTON_PRESS and ACTION_BUTTON_RELEASE the
188             // returned value of getActionButton() is undefined.
189             assertEquals(mCurrentTestCase + " (action button)",
190                     mLastButtonState ^ event.getButtonState(), event.getActionButton());
191             mLastButtonState = event.getButtonState();
192         }
193         assertAxis(mCurrentTestCase, expectedEvent, event);
194     }
195 
196     /**
197      * Asserts motion event axis values. Separate this into a different method to allow individual
198      * test case to specify it.
199      *
200      * @param expectedEvent expected event flag specified in JSON files.
201      * @param actualEvent actual event flag received in the test app.
202      */
assertAxis(String testCase, MotionEvent expectedEvent, MotionEvent actualEvent)203     void assertAxis(String testCase, MotionEvent expectedEvent, MotionEvent actualEvent) {
204         for (int i = 0; i < actualEvent.getPointerCount(); i++) {
205             for (int axis = MotionEvent.AXIS_X; axis <= MotionEvent.AXIS_GENERIC_16; axis++) {
206                 if (IGNORE_AXES.contains(axis)) continue;
207                 assertEquals(testCase + " pointer " + i
208                                 + " (" + MotionEvent.axisToString(axis) + ")",
209                         expectedEvent.getAxisValue(axis, i), actualEvent.getAxisValue(axis, i),
210                         TOLERANCE);
211             }
212         }
213     }
214 
215     /**
216      * Asserts source flags. Separate this into a different method to allow individual test case to
217      * specify it.
218      * The input source check verifies if actual source is equal or a subset of the expected source.
219      * With Linux kernel 4.18 or later the input hid driver could register multiple evdev devices
220      * when the HID descriptor has HID usages for different applications. Android frameworks will
221      * create multiple KeyboardInputMappers for each of the evdev device, and each
222      * KeyboardInputMapper will generate key events with source of the evdev device it belongs to.
223      * As long as the source of these key events is a subset of expected source, we consider it as
224      * a valid source.
225      *
226      * @param expected expected event with source flag specified in JSON files.
227      * @param actual actual event with source flag received in the test app.
228      */
assertSource(String testCase, InputEvent expected, InputEvent actual)229     private void assertSource(String testCase, InputEvent expected, InputEvent actual) {
230         assertNotEquals(testCase + " (source)", InputDevice.SOURCE_CLASS_NONE, actual.getSource());
231         assertTrue(testCase + " (source)", expected.isFromSource(actual.getSource()));
232     }
233 
234     /**
235      * Asserts meta states. Separate this into a different method to allow individual test case to
236      * specify it.
237      *
238      * @param expectedMetaState expected meta state specified in JSON files.
239      * @param actualMetaState actual meta state received in the test app.
240      */
assertMetaState(String testCase, int expectedMetaState, int actualMetaState)241     void assertMetaState(String testCase, int expectedMetaState, int actualMetaState) {
242         assertEquals(testCase + " (meta state)", expectedMetaState, actualMetaState);
243     }
244 
245     /**
246      * Assert that no more events have been received by the application.
247      *
248      * If any more events have been received by the application, this will cause failure.
249      */
assertNoMoreEvents()250     protected void assertNoMoreEvents() {
251         mInstrumentation.waitForIdleSync();
252         InputEvent event = mEvents.poll();
253         if (event == null) {
254             return;
255         }
256         failWithMessage("extraneous events generated: " + event);
257     }
258 
verifyEvents(List<InputEvent> events)259     protected void verifyEvents(List<InputEvent> events) {
260         verifyFirstEvents(events);
261         assertNoMoreEvents();
262     }
263 
verifyFirstEvents(List<InputEvent> events)264     private void verifyFirstEvents(List<InputEvent> events) {
265         // Make sure we received the expected input events
266         if (events.size() == 0) {
267             // If no event is expected we need to wait for event until timeout and fail on
268             // any unexpected event received caused by the HID report injection.
269             InputEvent event = waitForEvent();
270             if (event != null) {
271                 failWithMessage("Received unexpected event " + event);
272             }
273             return;
274         }
275         for (int i = 0; i < events.size(); i++) {
276             final InputEvent event = events.get(i);
277             try {
278                 if (event instanceof MotionEvent) {
279                     assertReceivedMotionEvent((MotionEvent) event);
280                     continue;
281                 }
282                 if (event instanceof KeyEvent) {
283                     assertReceivedKeyEvent((KeyEvent) event);
284                     continue;
285                 }
286             } catch (AssertionError error) {
287                 failWithMessage("Assertion on entry " + i + " failed: " + error);
288             }
289             failWithMessage("Entry " + i + " is neither a KeyEvent nor a MotionEvent: " + event);
290         }
291     }
292 
verifyNoKeyEvents()293     protected void verifyNoKeyEvents() {
294         InputEvent event = waitForEvent();
295         while (event != null) {
296             if (event instanceof KeyEvent) {
297                 failWithMessage(" : Received unexpected KeyEvent " + event);
298             }
299             event = waitForEvent();
300         }
301     }
302 
waitForEvent()303     private InputEvent waitForEvent() {
304         try {
305             return mEvents.poll(1, TimeUnit.SECONDS);
306         } catch (InterruptedException e) {
307             failWithMessage("unexpectedly interrupted while waiting for InputEvent");
308             return null;
309         }
310     }
311 
312     /**
313      * Try polling the events queue till a Key event is received. Ignore Motion events received
314      * during the attempts, and return the first Key event received.
315      */
waitForKey()316     private KeyEvent waitForKey() {
317         for (int i = 0; i < NUM_MAX_ATTEMPTS_TO_RECEIVE_SINGLE_EVENT; i++) {
318             InputEvent event = waitForEvent();
319             if (event instanceof KeyEvent) {
320                 return (KeyEvent) event;
321             }
322         }
323         return null;
324     }
325 
326     /**
327      * Try polling the events queue till a Motion event is received. Ignore Key events received
328      * during the attempts, and return the first Motion event received.
329      */
waitForMotion()330     private MotionEvent waitForMotion() {
331         for (int i = 0; i < NUM_MAX_ATTEMPTS_TO_RECEIVE_SINGLE_EVENT; i++) {
332             InputEvent event = waitForEvent();
333             if (event instanceof MotionEvent) {
334                 return (MotionEvent) event;
335             }
336         }
337         return null;
338     }
339 
340     /**
341      * Since MotionEvents are batched together based on overall system timings (i.e. vsync), we
342      * can't rely on them always showing up batched in the same way. In order to make sure our
343      * test results are consistent, we instead split up the batches so they end up in a
344      * consistent and reproducible stream.
345      *
346      * Note, however, that this ignores the problem of resampling, as we still don't know how to
347      * distinguish resampled events from real events. Only the latter will be consistent and
348      * reproducible.
349      *
350      * @param event The (potentially) batched MotionEvent
351      * @return List of MotionEvents, with each event guaranteed to have zero history size, and
352      * should otherwise be equivalent to the original batch MotionEvent.
353      */
splitBatchedMotionEvent(MotionEvent event)354     private static List<MotionEvent> splitBatchedMotionEvent(MotionEvent event) {
355         List<MotionEvent> events = new ArrayList<>();
356         final int historySize = event.getHistorySize();
357         final int pointerCount = event.getPointerCount();
358         MotionEvent.PointerProperties[] properties =
359                 new MotionEvent.PointerProperties[pointerCount];
360         MotionEvent.PointerCoords[] currentCoords = new MotionEvent.PointerCoords[pointerCount];
361         for (int p = 0; p < pointerCount; p++) {
362             properties[p] = new MotionEvent.PointerProperties();
363             event.getPointerProperties(p, properties[p]);
364             currentCoords[p] = new MotionEvent.PointerCoords();
365             event.getPointerCoords(p, currentCoords[p]);
366         }
367         for (int h = 0; h < historySize; h++) {
368             long eventTime = event.getHistoricalEventTime(h);
369             MotionEvent.PointerCoords[] coords = new MotionEvent.PointerCoords[pointerCount];
370 
371             for (int p = 0; p < pointerCount; p++) {
372                 coords[p] = new MotionEvent.PointerCoords();
373                 event.getHistoricalPointerCoords(p, h, coords[p]);
374             }
375             MotionEvent singleEvent =
376                     MotionEvent.obtain(event.getDownTime(), eventTime, event.getAction(),
377                             pointerCount, properties, coords,
378                             event.getMetaState(), event.getButtonState(),
379                             event.getXPrecision(), event.getYPrecision(),
380                             event.getDeviceId(), event.getEdgeFlags(),
381                             event.getSource(), event.getFlags());
382             singleEvent.setActionButton(event.getActionButton());
383             events.add(singleEvent);
384         }
385 
386         MotionEvent singleEvent =
387                 MotionEvent.obtain(event.getDownTime(), event.getEventTime(), event.getAction(),
388                         pointerCount, properties, currentCoords,
389                         event.getMetaState(), event.getButtonState(),
390                         event.getXPrecision(), event.getYPrecision(),
391                         event.getDeviceId(), event.getEdgeFlags(),
392                         event.getSource(), event.getFlags());
393         singleEvent.setActionButton(event.getActionButton());
394         events.add(singleEvent);
395         return events;
396     }
397 
398     /**
399      * Append the name of the currently executing test case to the fail message.
400      * Dump out the events queue to help debug.
401      */
failWithMessage(String message)402     private void failWithMessage(String message) {
403         DebugInputRule.dumpInputStateToLogcat();
404         if (mEvents.isEmpty()) {
405             Log.i(TAG, "The events queue is empty");
406         } else {
407             Log.e(TAG, "There are additional events received by the test activity:");
408             for (InputEvent event : mEvents) {
409                 Log.i(TAG, event.toString());
410             }
411         }
412         fail(mCurrentTestCase + ": " + message);
413     }
414 
setConsumeGenericMotionEvents(boolean enable)415     void setConsumeGenericMotionEvents(boolean enable) {
416         mTestActivity.setConsumeGenericMotionEvents(enable);
417     }
418 
419     private class InputListener implements InputCallback {
420         @Override
onKeyEvent(KeyEvent ev)421         public void onKeyEvent(KeyEvent ev) {
422             try {
423                 mEvents.put(new KeyEvent(ev));
424             } catch (InterruptedException ex) {
425                 failWithMessage("interrupted while adding a KeyEvent to the queue");
426             }
427         }
428 
429         @Override
onMotionEvent(MotionEvent ev)430         public void onMotionEvent(MotionEvent ev) {
431             try {
432                 for (MotionEvent event : splitBatchedMotionEvent(ev)) {
433                     mEvents.put(event);
434                 }
435             } catch (InterruptedException ex) {
436                 failWithMessage("interrupted while adding a MotionEvent to the queue");
437             }
438         }
439     }
440 
441     protected class PointerCaptureSession implements AutoCloseable {
PointerCaptureSession()442         protected PointerCaptureSession() {
443             ensurePointerCaptureState(true);
444         }
445 
446         @Override
close()447         public void close() {
448             ensurePointerCaptureState(false);
449         }
450 
ensurePointerCaptureState(boolean enable)451         private void ensurePointerCaptureState(boolean enable) {
452             final CountDownLatch latch = new CountDownLatch(1);
453             mTestActivity.setPointerCaptureCallback(hasCapture -> {
454                 if (enable == hasCapture) {
455                     latch.countDown();
456                 }
457             });
458             mTestActivity.runOnUiThread(enable ? mDecorView::requestPointerCapture
459                     : mDecorView::releasePointerCapture);
460             try {
461                 if (!latch.await(60, TimeUnit.SECONDS)) {
462                     throw new IllegalStateException(
463                             "Did not receive callback after "
464                                     + (enable ? "enabling" : "disabling")
465                                     + " Pointer Capture.");
466                 }
467             } catch (InterruptedException e) {
468                 throw new IllegalStateException(
469                         "Interrupted while waiting for Pointer Capture state.");
470             } finally {
471                 mTestActivity.setPointerCaptureCallback(null);
472             }
473             assertEquals("The view's Pointer Capture state did not match.", enable,
474                     mDecorView.hasPointerCapture());
475         }
476     }
477 }
478