| Index: remoting/android/javatests/src/org/chromium/chromoting/MockInputStub.java
|
| diff --git a/remoting/android/javatests/src/org/chromium/chromoting/MockInputStub.java b/remoting/android/javatests/src/org/chromium/chromoting/MockInputStub.java
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..b7b899123164410e8af7c0a1fb1a099037d73209
|
| --- /dev/null
|
| +++ b/remoting/android/javatests/src/org/chromium/chromoting/MockInputStub.java
|
| @@ -0,0 +1,408 @@
|
| +// Copyright 2016 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +package org.chromium.chromoting;
|
| +
|
| +import android.graphics.PointF;
|
| +
|
| +import junit.framework.Assert;
|
| +
|
| +import org.chromium.base.annotations.SuppressFBWarnings;
|
| +import org.chromium.chromoting.jni.TouchEventData;
|
| +
|
| +import java.util.Arrays;
|
| +import java.util.LinkedList;
|
| +
|
| +/**
|
| + * A mock implementation of {@link InputInjector} for testing purpose.
|
| + */
|
| +public final class MockInputStub extends Assert implements InputStub {
|
| + /** The base class to store an event, which represents an user activity. */
|
| + public abstract static class Event<T> {
|
| + public void assertEventEquals(T other) {
|
| + if (other == this) {
|
| + return;
|
| + }
|
| + if (other == null) {
|
| + fail();
|
| + }
|
| +
|
| + assertContentMatch(other);
|
| + }
|
| +
|
| + protected abstract void assertContentMatch(T other);
|
| + }
|
| +
|
| + /** A mouse event. */
|
| + public static final class MouseEvent extends Event<MouseEvent> {
|
| + private final int mX;
|
| + private final int mY;
|
| + private final int mButton;
|
| + private final boolean mDown;
|
| +
|
| + public MouseEvent(int x, int y, int button, boolean down) {
|
| + mX = x;
|
| + mY = y;
|
| + mButton = button;
|
| + mDown = down;
|
| + }
|
| +
|
| + @Override
|
| + protected void assertContentMatch(MouseEvent other) {
|
| + assertEquals(mX, other.mX);
|
| + assertEquals(mY, other.mY);
|
| + assertEquals(mButton, other.mButton);
|
| + assertEquals(mDown, other.mDown);
|
| + }
|
| + }
|
| +
|
| + /** A mouse wheel event. */
|
| + public static final class WheelEvent extends Event<WheelEvent> {
|
| + private final int mDeltaX;
|
| + private final int mDeltaY;
|
| +
|
| + public WheelEvent(int deltaX, int deltaY) {
|
| + mDeltaX = deltaX;
|
| + mDeltaY = deltaY;
|
| + }
|
| +
|
| + @Override
|
| + protected void assertContentMatch(WheelEvent other) {
|
| + assertEquals(mDeltaX, other.mDeltaX);
|
| + assertEquals(mDeltaY, other.mDeltaY);
|
| + }
|
| + }
|
| +
|
| + /** A keyboard event. */
|
| + public static final class KeyEvent extends Event<KeyEvent> {
|
| + private final int mScanCode;
|
| + private final int mKeyCode;
|
| + private final boolean mKeyDown;
|
| +
|
| + public KeyEvent(int scanCode, int keyCode, boolean keyDown) {
|
| + mScanCode = scanCode;
|
| + mKeyCode = keyCode;
|
| + mKeyDown = keyDown;
|
| + }
|
| +
|
| + @Override
|
| + protected void assertContentMatch(KeyEvent other) {
|
| + assertEquals(mScanCode, other.mScanCode);
|
| + assertEquals(mKeyCode, other.mKeyCode);
|
| + assertEquals(mKeyDown, other.mKeyDown);
|
| + }
|
| + }
|
| +
|
| + /** A text event. */
|
| + public static final class TextEvent extends Event<TextEvent> {
|
| + public final String mText;
|
| +
|
| + public TextEvent(String text) {
|
| + mText = text;
|
| + }
|
| +
|
| + @Override
|
| + protected void assertContentMatch(TextEvent other) {
|
| + if (mText == null && other.mText == null) {
|
| + return;
|
| + }
|
| + if (mText == null || other.mText == null) {
|
| + fail();
|
| + }
|
| + assertEquals(mText, other.mText);
|
| + }
|
| + }
|
| +
|
| + /** A touch event. */
|
| + public static final class TouchEvent extends Event<TouchEvent> {
|
| + /**
|
| + * A random number to represent an invalid touch id for {@link assertContentMatch}.
|
| + * Consumers can use this number to ignore the comparison of
|
| + * {@link TouchEventData#getTouchPointId()} when comparing.
|
| + */
|
| + public static final int INVALID_ID = -883;
|
| +
|
| + /**
|
| + * A random number to represent an invalid position for {@link #assertContentMatch}.
|
| + * Consumers can use this number to ignore certain float fields in an {@link TouchEventData}
|
| + * when comparing.
|
| + */
|
| + public static final float INVALID_POSITION = -763.273f;
|
| +
|
| + /**
|
| + * A number to represent an invalid angle in radians for {@link #assertContentMatch}.
|
| + * Consumers can use this number to ignore the comparison of
|
| + * {@link TouchEventData#getTouchPointAngle} when comparing.
|
| + */
|
| + public static final float INVALID_RADIANS = 6.289f * (float) Math.PI;
|
| +
|
| + // TouchEventData stores degrees instead of radians
|
| + private static final float INVALID_DEGREES = (float) Math.toDegrees(INVALID_RADIANS);
|
| + private final TouchEventData.EventType mEventType;
|
| + private final TouchEventData[] mData;
|
| +
|
| + public TouchEvent(TouchEventData.EventType eventType, TouchEventData[] data) {
|
| + mEventType = eventType;
|
| + mData = (data == null ? null : Arrays.copyOf(data, data.length));
|
| + }
|
| +
|
| + private static boolean idMatch(int left, int right) {
|
| + return left == right || left == INVALID_ID || right == INVALID_ID;
|
| + }
|
| +
|
| + private static boolean positionMatch(float left, float right) {
|
| + return left == right || left == INVALID_POSITION || right == INVALID_POSITION;
|
| + }
|
| +
|
| + private static boolean degreeMatch(float left, float right) {
|
| + return left == right || left == INVALID_DEGREES || right == INVALID_DEGREES;
|
| + }
|
| +
|
| + private static void assertContentMatch(TouchEventData left, TouchEventData right) {
|
| + assertTrue(idMatch(left.getTouchPointId(), right.getTouchPointId()));
|
| + assertTrue(positionMatch(left.getTouchPointX(), right.getTouchPointX()));
|
| + assertTrue(positionMatch(left.getTouchPointY(), right.getTouchPointY()));
|
| + assertTrue(positionMatch(left.getTouchPointRadiusX(), right.getTouchPointRadiusX()));
|
| + assertTrue(positionMatch(left.getTouchPointRadiusY(), right.getTouchPointRadiusY()));
|
| + assertTrue(degreeMatch(left.getTouchPointAngle(), right.getTouchPointAngle()));
|
| + assertTrue(positionMatch(left.getTouchPointPressure(), right.getTouchPointPressure()));
|
| + }
|
| +
|
| + private static void assertDataEquals(TouchEventData left, TouchEventData right) {
|
| + if (left == null && right == null) {
|
| + return;
|
| + }
|
| + if (left == null || right == null) {
|
| + fail();
|
| + }
|
| + assertContentMatch(left, right);
|
| + }
|
| +
|
| + private int dataSize() {
|
| + return mData == null ? 0 : mData.length;
|
| + }
|
| +
|
| + @Override
|
| + protected void assertContentMatch(TouchEvent other) {
|
| + assertEquals(mEventType, other.mEventType);
|
| + // An event with an empty mData array will match an event with a non-empty mData array.
|
| + // This is useful for tests which expect a TouchEvent with a certain EventType, but
|
| + // don't care about the content of mData.
|
| + if (dataSize() != 0 && other.dataSize() != 0) {
|
| + assertEquals(dataSize(), other.dataSize());
|
| + for (int i = 0; i < dataSize(); i++) {
|
| + assertDataEquals(mData[i], other.mData[i]);
|
| + }
|
| + }
|
| + }
|
| + }
|
| +
|
| + private final LinkedList<MouseEvent> mMouseEvents;
|
| + private final LinkedList<WheelEvent> mWheelEvents;
|
| + private final LinkedList<KeyEvent> mKeyEvents;
|
| + private final LinkedList<TextEvent> mTextEvents;
|
| + private final LinkedList<TouchEvent> mTouchEvents;
|
| +
|
| + public MockInputStub() {
|
| + mMouseEvents = new LinkedList<>();
|
| + mWheelEvents = new LinkedList<>();
|
| + mKeyEvents = new LinkedList<>();
|
| + mTextEvents = new LinkedList<>();
|
| + mTouchEvents = new LinkedList<>();
|
| + }
|
| +
|
| + /**
|
| + * Compares the first |right|.length events with the ones in |left|, asserts they are equal, and
|
| + * consumes these events from |left|.
|
| + */
|
| + private static <E extends Event<E>> void assertContains(
|
| + LinkedList<E> left, E[] right) {
|
| + if (left == null && right == null) {
|
| + return;
|
| + }
|
| + if (left == null || right == null) {
|
| + fail();
|
| + }
|
| + assertTrue("left.size() " + left.size() + " != right.length " + right.length,
|
| + left.size() >= right.length);
|
| + for (int i = 0; i < right.length; i++) {
|
| + E leftEvent = left.removeFirst();
|
| + if (leftEvent == null) {
|
| + assertNull(right[i]);
|
| + } else {
|
| + leftEvent.assertEventEquals(right[i]);
|
| + }
|
| + }
|
| + }
|
| +
|
| + public void clear() {
|
| + mMouseEvents.clear();
|
| + mWheelEvents.clear();
|
| + mKeyEvents.clear();
|
| + mTextEvents.clear();
|
| + mTouchEvents.clear();
|
| + }
|
| +
|
| + /**
|
| + * Compares the first |other|.length events with {@link #mMouseEvents} in this instance,
|
| + * asserts they are equal, and consumes these events in this instance.
|
| + */
|
| + public void assertContainsMouseEvents(MouseEvent[] other) {
|
| + assertContains(mMouseEvents, other);
|
| + }
|
| +
|
| + /**
|
| + * Compares the first |other|.length events with {@link #mTouchEvents} in this instance,
|
| + * asserts they are equal, and consumes these events in this instance.
|
| + */
|
| + public void assertContainsTouchEvents(TouchEvent[] other) {
|
| + assertContains(mTouchEvents, other);
|
| + }
|
| +
|
| + /** Asserts current instance is empty. */
|
| + public void assertEmpty() {
|
| + assertTrue(mMouseEvents.isEmpty());
|
| + assertTrue(mWheelEvents.isEmpty());
|
| + assertTrue(mKeyEvents.isEmpty());
|
| + assertTrue(mTextEvents.isEmpty());
|
| + assertTrue(mTouchEvents.isEmpty());
|
| + }
|
| +
|
| + /**
|
| + * Checks whether the first two events in {@link #mTouchEvents} are representing a down and
|
| + * an up action at specified position |x|, |y|, and consumes these events.
|
| + */
|
| + public void assertTapInjected(float x, float y) {
|
| + assertTouchEventInjected(TouchEventData.EventType.TOUCH_EVENT_START, x, y);
|
| + assertTouchEventInjected(TouchEventData.EventType.TOUCH_EVENT_END, x, y);
|
| + }
|
| +
|
| + /**
|
| + * Checks whether the first event in {@link #mTouchEvents} is representing an event with type
|
| + * |eventType| and at specified position |x|, |y|, and consumes this event.
|
| + */
|
| + public void assertTouchEventInjected(TouchEventData.EventType eventType, float x, float y) {
|
| + assertContainsTouchEvents(new TouchEvent[] {
|
| + new TouchEventBuilder()
|
| + .withEventType(eventType)
|
| + .withX(x)
|
| + .withY(y)
|
| + .append()
|
| + .build(),
|
| + });
|
| + }
|
| +
|
| + /**
|
| + * Checks whether the first event in {@link #mTouchEvents} is representing an event with type
|
| + * |eventType|, and consumes this event.
|
| + */
|
| + public void assertTouchEventInjected(TouchEventData.EventType eventType) {
|
| + assertContainsTouchEvents(new TouchEvent[] {
|
| + new TouchEventBuilder().withEventType(eventType).build(),
|
| + });
|
| + }
|
| +
|
| + /**
|
| + * Checks whether the first two events in {@link #mMouseEvents} are representing a down and an
|
| + * up action with |button| at specified position |x|, |y|, and consumes these events.
|
| + */
|
| + public void assertClickInjected(int button, int x, int y) {
|
| + assertContainsMouseEvents(new MouseEvent[] {
|
| + new MouseEvent(x, y, button, true), new MouseEvent(x, y, button, false),
|
| + });
|
| + }
|
| +
|
| + public void assertTouchMoveEventInjected(
|
| + PointF[] initPositions, int stepX, int stepY, int moveCount) {
|
| + LinkedList<TouchEvent> events = new LinkedList<>();
|
| + assertTrue(initPositions != null && initPositions.length > 0);
|
| + for (int i = 0; i < initPositions.length; i++) {
|
| + assertNotNull(initPositions[i]);
|
| + events.add(new TouchEventBuilder()
|
| + .withEventType(TouchEventData.EventType.TOUCH_EVENT_START)
|
| + .withX(initPositions[i].x)
|
| + .withY(initPositions[i].y)
|
| + .append()
|
| + .build());
|
| + }
|
| +
|
| + // These tests send a single event for each finger that is moved. E.g. if we are injecting
|
| + // a pan event with two fingers, then every two TouchEvents represents one 'frame' of the
|
| + // motion sequence. Here we determine which iteration this event represents so we can use
|
| + // that to accurately compare the locations with the expected values.
|
| + for (int i = 0; i < moveCount; i++) {
|
| + for (int j = 0; j < initPositions.length; j++) {
|
| + TouchEventBuilder builder = new TouchEventBuilder();
|
| + builder.withEventType(TouchEventData.EventType.TOUCH_EVENT_MOVE);
|
| + for (int k = 0; k < initPositions.length; k++) {
|
| + // We inject one finger at a time which means that finger will have the correct
|
| + // value but other fingers may still be stepSize behind it.
|
| + int currentStep = i - 1;
|
| + if (j >= k) {
|
| + currentStep++;
|
| + }
|
| + if (currentStep < 0) {
|
| + currentStep = 0;
|
| + }
|
| + builder.withX(initPositions[k].x + currentStep * stepX)
|
| + .withY(initPositions[k].y + currentStep * stepY)
|
| + .append();
|
| + }
|
| + events.add(builder.build());
|
| + }
|
| + }
|
| +
|
| + assertContainsTouchEvents(events.toArray(new TouchEvent[0]));
|
| + }
|
| +
|
| + /**
|
| + * Checks whether the first two events in {@link #mMouseEvents} are representing a down and an
|
| + * up action with right button at specified position |x|, |y|, and consumes these events.
|
| + */
|
| + public void assertRightClickInjected(int x, int y) {
|
| + assertClickInjected(BUTTON_RIGHT, x, y);
|
| + }
|
| +
|
| + // ---------------- Implementations of InputInjector ----------------------
|
| + @Override
|
| + public void sendMouseEvent(int x, int y, int whichButton, boolean buttonDown) {
|
| + mMouseEvents.add(new MouseEvent(x, y, whichButton, buttonDown));
|
| + }
|
| +
|
| + @Override
|
| + public void sendMouseWheelEvent(int deltaX, int deltaY) {
|
| + mWheelEvents.add(new WheelEvent(deltaX, deltaY));
|
| + }
|
| +
|
| + @Override
|
| + public boolean sendKeyEvent(int scanCode, int keyCode, boolean keyDown) {
|
| + mKeyEvents.add(new KeyEvent(scanCode, keyCode, keyDown));
|
| +
|
| + // Note: This implementation is not consistent with jni.Client, which may return false when
|
| + // scanCode and keyCode cannot be mapped to a usb key code.
|
| + return true;
|
| + }
|
| +
|
| + @Override
|
| + public void sendTextEvent(String text) {
|
| + mTextEvents.add(new TextEvent(text));
|
| + }
|
| +
|
| + @SuppressFBWarnings("FE_FLOATING_POINT_EQUALITY")
|
| + @Override
|
| + public void sendTouchEvent(TouchEventData.EventType eventType, TouchEventData[] data) {
|
| + assertNotNull(data);
|
| + assertTrue(data.length != 0);
|
| + for (int i = 0; i < data.length; i++) {
|
| + assertTrue(data[i].getTouchPointId() != TouchEvent.INVALID_ID);
|
| + assertTrue(data[i].getTouchPointX() != TouchEvent.INVALID_POSITION);
|
| + assertTrue(data[i].getTouchPointY() != TouchEvent.INVALID_POSITION);
|
| + assertTrue(data[i].getTouchPointRadiusX() != TouchEvent.INVALID_POSITION);
|
| + assertTrue(data[i].getTouchPointRadiusY() != TouchEvent.INVALID_POSITION);
|
| + assertTrue(data[i].getTouchPointAngle() != TouchEvent.INVALID_DEGREES);
|
| + assertTrue(data[i].getTouchPointPressure() != TouchEvent.INVALID_POSITION);
|
| + }
|
| + mTouchEvents.add(new TouchEvent(eventType, data));
|
| + }
|
| +}
|
|
|