blob: c4bfb114a0d1b209dde4e02bad69f2dcad83526a [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.view.cts;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.InputDevice.SOURCE_JOYSTICK;
import static android.view.KeyEvent.FLAG_CANCELED;
import static android.view.KeyEvent.KEYCODE_A;
import static android.view.MotionEvent.FLAG_WINDOW_IS_OBSCURED;
import static android.view.MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.app.Instrumentation;
import android.app.UiAutomation;
import android.graphics.Point;
import android.hardware.input.InputManager;
import android.os.SystemClock;
import android.view.InputDevice;
import android.view.InputEvent;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.VerifiedInputEvent;
import android.view.VerifiedKeyEvent;
import android.view.VerifiedMotionEvent;
import android.view.View;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;
import com.android.compatibility.common.util.WindowUtil;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.Arrays;
import java.util.List;
/**
* Test {@link android.hardware.input.InputManager#verifyInputEvent(InputEvent)} functionality.
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
public class VerifyInputEventTest {
private static final int NANOS_PER_MILLISECOND = 1000000;
private static final float EPSILON = 0.001f;
private static final int INJECTED_EVENT_DEVICE_ID = KeyCharacterMap.VIRTUAL_KEYBOARD;
private InputManager mInputManager;
private UiAutomation mAutomation;
private InputEventInterceptTestActivity mActivity;
@Rule
public ActivityTestRule<InputEventInterceptTestActivity> mActivityRule =
new ActivityTestRule<>(InputEventInterceptTestActivity.class);
@Before
public void setup() {
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
mInputManager = instrumentation.getTargetContext().getSystemService(InputManager.class);
assertNotNull(mInputManager);
mAutomation = instrumentation.getUiAutomation();
mActivity = mActivityRule.getActivity();
WindowUtil.waitForFocus(mActivity);
}
@Test
public void testKeyEvent() {
final long downTime = SystemClock.uptimeMillis();
final int keyCode = KEYCODE_A;
KeyEvent downEvent = new KeyEvent(downTime, downTime,
KeyEvent.ACTION_DOWN, keyCode, 0 /* repeatCount */);
mAutomation.injectInputEvent(downEvent, true);
KeyEvent received = waitForKey();
VerifiedInputEvent verified = mInputManager.verifyInputEvent(received);
assertNotNull(verified);
compareKeys(downEvent, verified);
// Send UP event for consistency
KeyEvent upEvent = new KeyEvent(downTime, SystemClock.uptimeMillis(),
KeyEvent.ACTION_UP, keyCode, 0 /* repeatCount */);
mAutomation.injectInputEvent(upEvent, true);
waitForKey();
}
/**
* Try to verify an event that did not come from system, and therefore does not have an hmac
* set properly.
* Verification should fail.
*/
@Test
public void testKeyEventWithoutHmac() {
final long downTime = SystemClock.uptimeMillis();
final int keyCode = KEYCODE_A;
KeyEvent downEvent = new KeyEvent(downTime, downTime,
KeyEvent.ACTION_DOWN, keyCode, 0 /* repeatCount */);
mAutomation.injectInputEvent(downEvent, true);
waitForKey(); // we will not be using the received event
VerifiedInputEvent verified = mInputManager.verifyInputEvent(downEvent);
assertNull(verified);
// Send UP event for consistency
KeyEvent upEvent = new KeyEvent(downTime, SystemClock.uptimeMillis(),
KeyEvent.ACTION_UP, keyCode, 0 /* repeatCount */);
mAutomation.injectInputEvent(upEvent, true);
waitForKey();
}
/**
* Try to verify an event that came from system, but has been tempered with.
* Verification should fail.
*/
@Test
public void testTamperedKeyEvent() {
final long downTime = SystemClock.uptimeMillis();
final int keyCode = KEYCODE_A;
KeyEvent downEvent = new KeyEvent(downTime, downTime,
KeyEvent.ACTION_DOWN, keyCode, 0 /* repeatCount */);
mAutomation.injectInputEvent(downEvent, true);
KeyEvent received = waitForKey();
received.setSource(SOURCE_JOYSTICK); // use the received event, but modify its source
VerifiedInputEvent verified = mInputManager.verifyInputEvent(received);
assertNull(verified);
// Send UP event for consistency
KeyEvent upEvent = new KeyEvent(downTime, SystemClock.uptimeMillis(),
KeyEvent.ACTION_UP, keyCode, 0 /* repeatCount */);
mAutomation.injectInputEvent(upEvent, true);
waitForKey();
}
@Test
public void testMotionEvent() {
final View view = mActivity.getWindow().getDecorView();
final Point point = getViewCenterOnScreen(view);
final long downTime = SystemClock.uptimeMillis();
MotionEvent downEvent = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_DOWN,
point.x, point.y, 0 /*metaState*/);
downEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN);
mAutomation.injectInputEvent(downEvent, true);
MotionEvent received = waitForMotion();
VerifiedInputEvent verified = mInputManager.verifyInputEvent(received);
assertNotNull(verified);
compareMotions(downEvent, verified);
// Send UP event for consistency
MotionEvent upEvent = MotionEvent.obtain(downTime, SystemClock.uptimeMillis(),
MotionEvent.ACTION_UP, point.x, point.y, 0 /*metaState*/);
upEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN);
mAutomation.injectInputEvent(upEvent, true);
waitForMotion();
}
/**
* Try to verify an event that did not come from system, and therefore does not have an hmac
* set properly.
* Verification should fail.
*/
@Test
public void testMotionEventWithoutHmac() {
final View view = mActivity.getWindow().getDecorView();
final Point point = getViewCenterOnScreen(view);
final long downTime = SystemClock.uptimeMillis();
MotionEvent downEvent = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_DOWN,
point.x, point.y, 0 /*metaState*/);
downEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN);
mAutomation.injectInputEvent(downEvent, true);
waitForMotion(); // we will not be using the received event
VerifiedInputEvent verified = mInputManager.verifyInputEvent(downEvent);
assertNull(verified);
// Send UP event for consistency
MotionEvent upEvent = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_UP,
point.x, point.y, 0 /*metaState*/);
upEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN);
mAutomation.injectInputEvent(upEvent, true);
waitForMotion();
}
/**
* Try to verify an event that came from system, but has been tempered with.
* Verification should fail.
*/
@Test
public void testTamperedMotionEvent() {
final View view = mActivity.getWindow().getDecorView();
final Point point = getViewCenterOnScreen(view);
final long downTime = SystemClock.uptimeMillis();
MotionEvent downEvent = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_DOWN,
point.x, point.y, 0 /*metaState*/);
downEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN);
mAutomation.injectInputEvent(downEvent, true);
MotionEvent received = waitForMotion();
// use the received event, by modify its action
received.setAction(MotionEvent.ACTION_CANCEL);
VerifiedInputEvent verified = mInputManager.verifyInputEvent(received);
assertNull(verified);
// Send UP event for consistency
MotionEvent upEvent = MotionEvent.obtain(downTime, SystemClock.uptimeMillis(),
MotionEvent.ACTION_UP, point.x, point.y, 0 /*metaState*/);
upEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN);
mAutomation.injectInputEvent(upEvent, true);
waitForMotion();
}
/**
* Ensure that injected key events that contain a real device id get injected as virtual
* device events, to prevent misrepresentation of actual hardware.
* The verified events should contain the virtual device id, which is consistent with what the
* app receives.
*/
@Test
public void testDeviceIdBecomesVirtualForInjectedKeys() {
final long downTime = SystemClock.uptimeMillis();
KeyEvent downEvent = new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
KeyEvent.KEYCODE_A, 0 /*repeat*/, 0 /*metaState*/,
1/*deviceId*/, 0 /*scanCode*/);
mAutomation.injectInputEvent(downEvent, true);
KeyEvent received = waitForKey();
assertEquals(INJECTED_EVENT_DEVICE_ID, received.getDeviceId());
// This event can still be verified, however.
VerifiedInputEvent verified = mInputManager.verifyInputEvent(received);
assertEquals(INJECTED_EVENT_DEVICE_ID, verified.getDeviceId());
// Send UP event for consistency
KeyEvent upEvent = new KeyEvent(downTime, SystemClock.uptimeMillis(), KeyEvent.ACTION_UP,
KeyEvent.KEYCODE_A, 0 /*repeat*/, 0 /*metaState*/,
1/*deviceId*/, 0 /*scanCode*/);
mAutomation.injectInputEvent(upEvent, true);
waitForKey();
}
/**
* Ensure that injected motion events that contain a real device id get injected as virtual
* device events, to prevent misrepresentation of actual hardware.
* The verified events should contain the virtual device id, which is consistent with what the
* app receives.
*/
@Test
public void testDeviceIdBecomesVirtualForInjectedMotions() {
final View view = mActivity.getWindow().getDecorView();
final Point point = getViewCenterOnScreen(view);
final long downTime = SystemClock.uptimeMillis();
MotionEvent downEvent = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_DOWN,
point.x, point.y, 1 /*pressure*/, 1 /*size*/, 0 /*metaState*/,
0 /*xPrecision*/, 0 /*yPrecision*/, 1 /*deviceId*/, 0 /*edgeFlags*/);
downEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN);
mAutomation.injectInputEvent(downEvent, true);
MotionEvent received = waitForMotion();
assertEquals(INJECTED_EVENT_DEVICE_ID, received.getDeviceId());
// This event can still be verified, however.
VerifiedInputEvent verified = mInputManager.verifyInputEvent(received);
assertEquals(INJECTED_EVENT_DEVICE_ID, verified.getDeviceId());
// Send UP event for consistency
MotionEvent upEvent = MotionEvent.obtain(downTime, SystemClock.uptimeMillis(),
MotionEvent.ACTION_UP, point.x, point.y, 0 /*pressure*/, 1 /*size*/,
0 /*metaState*/, 0 /*xPrecision*/, 0 /*yPrecision*/,
1 /*deviceId*/, 0 /*edgeFlags*/);
upEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN);
mAutomation.injectInputEvent(upEvent, true);
waitForMotion();
}
private static Point getViewCenterOnScreen(View view) {
final int[] location = new int[2];
view.getLocationOnScreen(location);
final int width = view.getWidth();
final int height = view.getHeight();
return new Point(location[0] + width / 2, location[1] + height / 2);
}
private KeyEvent waitForKey() {
return mActivity.mKeyEvents.poll();
}
private MotionEvent waitForMotion() {
return mActivity.mMotionEvents.poll();
}
private static void compareKeys(KeyEvent keyEvent, VerifiedInputEvent verified) {
assertEquals(INJECTED_EVENT_DEVICE_ID, verified.getDeviceId());
assertEquals(keyEvent.getEventTime() * NANOS_PER_MILLISECOND,
verified.getEventTimeNanos());
assertEquals(keyEvent.getSource(), verified.getSource());
assertEquals(DEFAULT_DISPLAY, verified.getDisplayId());
assertTrue(verified instanceof VerifiedKeyEvent);
VerifiedKeyEvent verifiedKey = (VerifiedKeyEvent) verified;
assertEquals(keyEvent.getAction(), verifiedKey.getAction());
assertEquals(keyEvent.getDownTime() * NANOS_PER_MILLISECOND,
verifiedKey.getDownTimeNanos());
compareKeyFlags(keyEvent.getFlags(), verifiedKey);
assertEquals(keyEvent.getKeyCode(), verifiedKey.getKeyCode());
assertEquals(keyEvent.getScanCode(), verifiedKey.getScanCode());
assertEquals(keyEvent.getMetaState(), verifiedKey.getMetaState());
assertEquals(keyEvent.getRepeatCount(), verifiedKey.getRepeatCount());
}
private static void compareMotions(MotionEvent motionEvent, VerifiedInputEvent verified) {
assertEquals(INJECTED_EVENT_DEVICE_ID, verified.getDeviceId());
assertEquals(motionEvent.getEventTime() * NANOS_PER_MILLISECOND,
verified.getEventTimeNanos());
assertEquals(motionEvent.getSource(), verified.getSource());
assertEquals(DEFAULT_DISPLAY, verified.getDisplayId());
assertTrue(verified instanceof VerifiedMotionEvent);
VerifiedMotionEvent verifiedMotion = (VerifiedMotionEvent) verified;
assertEquals(motionEvent.getRawX(), verifiedMotion.getRawX(), EPSILON);
assertEquals(motionEvent.getRawY(), verifiedMotion.getRawY(), EPSILON);
assertEquals(motionEvent.getActionMasked(), verifiedMotion.getActionMasked());
assertEquals(motionEvent.getDownTime() * NANOS_PER_MILLISECOND,
verifiedMotion.getDownTimeNanos());
compareMotionFlags(motionEvent.getFlags(), verifiedMotion);
assertEquals(motionEvent.getMetaState(), verifiedMotion.getMetaState());
assertEquals(motionEvent.getButtonState(), verifiedMotion.getButtonState());
}
private static void compareKeyFlags(int expectedFlags, VerifiedKeyEvent verified) {
final List<Integer> verifiedKeyFlags = Arrays.asList(
FLAG_CANCELED,
KeyEvent.FLAG_IS_ACCESSIBILITY_EVENT);
// Separately check the value of verifiable flags
for (int flag : verifiedKeyFlags) {
assertFlag(expectedFlags, flag, verified);
}
// All other flags should be null, because they are not verifiable
for (int i = 0; i < Integer.SIZE; i++) {
int flag = 1 << i;
if (verifiedKeyFlags.contains(flag)) {
continue;
}
assertNull(verified.getFlag(flag));
}
}
private static void compareMotionFlags(int expectedFlags, VerifiedMotionEvent verified) {
final List<Integer> verifiedMotionFlags = Arrays.asList(
FLAG_WINDOW_IS_OBSCURED,
FLAG_WINDOW_IS_PARTIALLY_OBSCURED,
MotionEvent.FLAG_IS_ACCESSIBILITY_EVENT);
// Separately check the value of verifiable flags
for (int flag : verifiedMotionFlags) {
assertFlag(expectedFlags, flag, verified);
}
// All other flags should be null, because they are not verifiable
for (int i = 0; i < Integer.SIZE; i++) {
int flag = 1 << i;
if (verifiedMotionFlags.contains(flag)) {
continue;
}
assertNull(verified.getFlag(flag));
}
}
private static void assertFlag(int expectedFlags, int flag, VerifiedInputEvent verified) {
final Boolean actual;
if (verified instanceof VerifiedKeyEvent) {
actual = ((VerifiedKeyEvent) verified).getFlag(flag);
} else if (verified instanceof VerifiedMotionEvent) {
actual = ((VerifiedMotionEvent) verified).getFlag(flag);
} else {
fail("Unknown type of VerifiedInputEvent");
actual = null;
}
assertNotNull(actual);
boolean flagValue = (expectedFlags & flag) != 0;
assertEquals(flagValue, actual);
}
}