summaryrefslogtreecommitdiffstats
path: root/remoting/android
diff options
context:
space:
mode:
authorjoedow <joedow@chromium.org>2015-12-17 19:16:01 -0800
committerCommit bot <commit-bot@chromium.org>2015-12-18 03:17:06 +0000
commit4b901e425e8e051ab39bc133c2b54fd56cd369b9 (patch)
tree24cbfca32d442e91641d764f81605a74f2664447 /remoting/android
parent105685d9418165b9bb997a5ea3f9e9527097ada5 (diff)
downloadchromium_src-4b901e425e8e051ab39bc133c2b54fd56cd369b9.zip
chromium_src-4b901e425e8e051ab39bc133c2b54fd56cd369b9.tar.gz
chromium_src-4b901e425e8e051ab39bc133c2b54fd56cd369b9.tar.bz2
Adding TouchInputStrategy.
This change introduces the new TouchInputStrategy class. This class receives local touch input events and transforms them into remote touch events which are transmitted to the host for injection. This class is the counterpart to the SimulatedTouchInputStrategy, the difference being that this class is used when the remote host supports touch injection (vs. the Simulated...Strategy which is used for mouse only hosts). In order to inject remote touch events, this class keeps a queue of all received MotionEvents which it then injects if the appropriate gesture is detected. Once a gesture has started, then events are injected as they are received. BUG=454549 Review URL: https://codereview.chromium.org/1525203002 Cr-Commit-Position: refs/heads/master@{#366001}
Diffstat (limited to 'remoting/android')
-rw-r--r--remoting/android/java/src/org/chromium/chromoting/DesktopView.java12
-rw-r--r--remoting/android/java/src/org/chromium/chromoting/TouchInputStrategy.java316
2 files changed, 323 insertions, 5 deletions
diff --git a/remoting/android/java/src/org/chromium/chromoting/DesktopView.java b/remoting/android/java/src/org/chromium/chromoting/DesktopView.java
index 4842bd1..0c2330b 100644
--- a/remoting/android/java/src/org/chromium/chromoting/DesktopView.java
+++ b/remoting/android/java/src/org/chromium/chromoting/DesktopView.java
@@ -408,8 +408,7 @@ public class DesktopView extends SurfaceView implements DesktopViewInterface,
/** Updates the current InputStrategy used by the TouchInputHandler. */
public void changeInputMode(
Desktop.InputMode inputMode, CapabilityManager.HostCapability hostTouchCapability) {
- // In order to set the correct input strategy, we need to know the current input mode and
- // the host input capabilities.
+ // We need both input mode and host input capabilities to select the input strategy.
if (!inputMode.isSet() || !hostTouchCapability.isSet()) {
return;
}
@@ -420,9 +419,12 @@ public class DesktopView extends SurfaceView implements DesktopViewInterface,
break;
case TOUCH:
- // TODO(joedow): Add TouchInputStrategy once it is finished.
- mInputHandler.setInputStrategy(
- new SimulatedTouchInputStrategy(mRenderData, getContext()));
+ if (hostTouchCapability.isSupported()) {
+ mInputHandler.setInputStrategy(new TouchInputStrategy(mRenderData));
+ } else {
+ mInputHandler.setInputStrategy(
+ new SimulatedTouchInputStrategy(mRenderData, getContext()));
+ }
break;
default:
diff --git a/remoting/android/java/src/org/chromium/chromoting/TouchInputStrategy.java b/remoting/android/java/src/org/chromium/chromoting/TouchInputStrategy.java
new file mode 100644
index 0000000..488760b
--- /dev/null
+++ b/remoting/android/java/src/org/chromium/chromoting/TouchInputStrategy.java
@@ -0,0 +1,316 @@
+// Copyright 2015 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.Matrix;
+import android.view.MotionEvent;
+
+import org.chromium.base.VisibleForTesting;
+import org.chromium.chromoting.jni.JniInterface;
+import org.chromium.chromoting.jni.TouchEventData;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+
+/**
+ * This class receives local touch input events and forwards them to the remote host.
+ * A queue of MotionEvents is built up and then either transmitted to the remote host if one of its
+ * remote gesture handler methods is called (such as onScroll) or it is cleared if the current
+ * stream of events does not represent a remote gesture.
+ * NOTE: Not all touch gestures are remoted. Touch input and gestures outside the supported ones
+ * (which includes tapping and 2 finger panning) will either affect the local canvas or
+ * will be dropped/ignored.
+ */
+public class TouchInputStrategy implements InputStrategyInterface {
+ /**
+ * This interface abstracts the injection mechanism to allow for unit testing.
+ */
+ public interface RemoteInputInjector {
+ public void injectMouseEvent(int x, int y, int button, boolean buttonDown);
+ public void injectTouchEvent(TouchEventData.EventType eventType, TouchEventData[] data);
+ }
+
+ /**
+ * This class provides the default implementation for injecting remote events.
+ */
+ private static class DefaultInputInjector implements RemoteInputInjector {
+ @Override
+ public void injectMouseEvent(int x, int y, int button, boolean buttonDown) {
+ JniInterface.sendMouseEvent(x, y, button, buttonDown);
+ }
+
+ @Override
+ public void injectTouchEvent(TouchEventData.EventType eventType, TouchEventData[] data) {
+ JniInterface.sendTouchEvent(eventType, data);
+ }
+ }
+
+ /**
+ * Contains the maximum number of MotionEvents to store before cancelling the current gesture.
+ * The size is ~3x the largest number of events seen during any remotable gesture sequence.
+ */
+ private static final int QUEUED_EVENT_THRESHOLD = 50;
+
+ /**
+ * Contains the set of MotionEvents received for the current gesture candidate. If one of the
+ * gesture handling methods is called, these queued events will be transmitted to the remote
+ * host for injection. The queue has a maximum size determined by |QUEUED_EVENT_THRESHOLD| to
+ * prevent a live memory leak where the queue grows unbounded during a local gesture (such as
+ * someone panning the local canvas continuously for several seconds/minutes).
+ */
+ private Queue<MotionEvent> mQueuedEvents;
+
+ /**
+ * Indicates that the events received should be treated as part of an active remote gesture.
+ */
+ private boolean mInRemoteGesture = false;
+
+ /**
+ * Indicates whether MotionEvents and gestures should be acted upon or ignored. This flag is
+ * set when we believe that the current sequence of events is not something we should remote.
+ */
+ private boolean mIgnoreTouchEvents = false;
+
+ private final RenderData mRenderData;
+
+ private RemoteInputInjector mRemoteInputInjector;
+
+ public TouchInputStrategy(RenderData renderData) {
+ mRenderData = renderData;
+ mRemoteInputInjector = new DefaultInputInjector();
+ mQueuedEvents = new LinkedList<MotionEvent>();
+
+ synchronized (mRenderData) {
+ mRenderData.drawCursor = false;
+ }
+ }
+
+ @Override
+ public boolean onTap(int button) {
+ if (mQueuedEvents.isEmpty() || mIgnoreTouchEvents) {
+ return false;
+ }
+
+ switch (button) {
+ case TouchInputHandlerInterface.BUTTON_LEFT:
+ injectQueuedEvents();
+ return true;
+
+ case TouchInputHandlerInterface.BUTTON_RIGHT:
+ // Using the mouse for right-clicking is consistent across all host platforms.
+ // Right-click gestures are often platform specific and can be tricky to simulate.
+
+ // Grab the first queued event which should be the initial ACTION_DOWN event.
+ MotionEvent downEvent = mQueuedEvents.peek();
+ assert downEvent.getActionMasked() == MotionEvent.ACTION_DOWN;
+
+ int x = (int) downEvent.getX();
+ int y = (int) downEvent.getY();
+ mRemoteInputInjector.injectMouseEvent(
+ x, y, TouchInputHandlerInterface.BUTTON_RIGHT, true);
+ mRemoteInputInjector.injectMouseEvent(
+ x, y, TouchInputHandlerInterface.BUTTON_RIGHT, false);
+ clearQueuedEvents();
+ return true;
+
+ default:
+ // Tap gestures for > 2 fingers are not supported.
+ return false;
+ }
+ }
+
+ @Override
+ public boolean onPressAndHold(int button) {
+ if (button != TouchInputHandlerInterface.BUTTON_LEFT || mQueuedEvents.isEmpty()
+ || mIgnoreTouchEvents) {
+ return false;
+ }
+
+ mInRemoteGesture = true;
+ injectQueuedEvents();
+ return true;
+ }
+
+ @Override
+ public void onScroll(float distanceX, float distanceY) {
+ if (mIgnoreTouchEvents || mInRemoteGesture) {
+ return;
+ }
+
+ mInRemoteGesture = true;
+ injectQueuedEvents();
+ }
+
+ @Override
+ public void onMotionEvent(MotionEvent event) {
+ // MotionEvents received are stored in a queue. This queue is added to until one of the
+ // gesture handling methods is called to indicate that a remote gesture is in progress. At
+ // that point, each enqueued MotionEvent is dequeued and transmitted to the remote machine
+ // and the class will now forward all MotionEvents received in real time until the gesture
+ // has been completed. If we receive too many events without having been notified to start
+ // a remote gesture, then the queue is cleared and we will wait until the start of the next
+ // gesture to begin queueing again.
+ int action = event.getActionMasked();
+ if (mIgnoreTouchEvents && action != MotionEvent.ACTION_DOWN) {
+ return;
+ } else if (mQueuedEvents.size() > QUEUED_EVENT_THRESHOLD) {
+ // Since we maintain a queue of events to replay once the gesture is known, we need to
+ // ensure that we do not continue to queue events when we are reasonably sure that the
+ // user action is not going to be sent to the remote host.
+ mIgnoreTouchEvents = true;
+ clearQueuedEvents();
+ return;
+ }
+
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ resetStateData();
+ mQueuedEvents.add(transformToRemoteCoordinates(event));
+ break;
+
+ case MotionEvent.ACTION_POINTER_DOWN:
+ if (mInRemoteGesture) {
+ // Cancel the current gesture if a pointer down action is seen during it.
+ // We do this because a new pointer down means that we are no longer performing
+ // the old gesture.
+ mIgnoreTouchEvents = true;
+ clearQueuedEvents();
+ } else {
+ mQueuedEvents.add(transformToRemoteCoordinates(event));
+ }
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_MOVE:
+ case MotionEvent.ACTION_POINTER_UP:
+ case MotionEvent.ACTION_UP:
+ event = transformToRemoteCoordinates(event);
+ if (mInRemoteGesture) {
+ injectTouchEventData(event);
+ event.recycle();
+ } else {
+ mQueuedEvents.add(event);
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ @Override
+ public void injectCursorMoveEvent(int x, int y) {}
+
+ @Override
+ public DesktopView.InputFeedbackType getShortPressFeedbackType() {
+ return DesktopView.InputFeedbackType.NONE;
+ }
+
+ @Override
+ public DesktopView.InputFeedbackType getLongPressFeedbackType() {
+ return DesktopView.InputFeedbackType.LARGE_ANIMATION;
+ }
+
+ @Override
+ public boolean isIndirectInputMode() {
+ return false;
+ }
+
+ @VisibleForTesting
+ protected void setRemoteInputInjectorForTest(RemoteInputInjector injector) {
+ mRemoteInputInjector = injector;
+ }
+
+ /**
+ * Extracts the touch point data from a MotionEvent, converts each point into a marshallable
+ * object and passes the set of points to the JNI layer to be transmitted to the remote host.
+ *
+ * @param event The event to send to the remote host for injection. NOTE: This object must be
+ * updated to represent the remote machine's coordinate system before calling this
+ * function. This should be done via the transformToRemoteCoordinates() method.
+ */
+ private void injectTouchEventData(MotionEvent event) {
+ int action = event.getActionMasked();
+ TouchEventData.EventType touchEventType = TouchEventData.EventType.fromMaskedAction(action);
+ List<TouchEventData> touchEventList = new ArrayList<TouchEventData>();
+
+ if (action == MotionEvent.ACTION_MOVE) {
+ // In order to process all of the events associated with an ACTION_MOVE event, we need
+ // to walk the list of historical events in order and add each event to our list, then
+ // retrieve the current move event data.
+ int pointerCount = event.getPointerCount();
+ int historySize = event.getHistorySize();
+ for (int h = 0; h < historySize; ++h) {
+ for (int p = 0; p < pointerCount; ++p) {
+ touchEventList.add(new TouchEventData(event.getPointerId(p),
+ event.getHistoricalX(p, h), event.getHistoricalY(p, h),
+ event.getHistoricalSize(p, h), event.getHistoricalSize(p, h),
+ event.getHistoricalOrientation(p, h),
+ event.getHistoricalPressure(p, h)));
+ }
+ }
+
+ for (int p = 0; p < pointerCount; p++) {
+ touchEventList.add(new TouchEventData(event.getPointerId(p), event.getX(p),
+ event.getY(p), event.getSize(p), event.getSize(p), event.getOrientation(p),
+ event.getPressure(p)));
+ }
+ } else {
+ // For all other events, we only want to grab the current/active pointer. The event
+ // contains a list of every active pointer but passing all of of these to the host can
+ // cause confusion on the remote OS side and result in broken touch gestures.
+ int activePointerIndex = event.getActionIndex();
+ touchEventList.add(new TouchEventData(event.getPointerId(activePointerIndex),
+ event.getX(activePointerIndex), event.getY(activePointerIndex),
+ event.getSize(activePointerIndex), event.getSize(activePointerIndex),
+ event.getOrientation(activePointerIndex),
+ event.getPressure(activePointerIndex)));
+ }
+
+ if (!touchEventList.isEmpty()) {
+ TouchEventData[] touchEventArray = new TouchEventData[touchEventList.size()];
+ mRemoteInputInjector.injectTouchEvent(
+ touchEventType, touchEventList.toArray(touchEventArray));
+ }
+ }
+
+ private void injectQueuedEvents() {
+ while (!mQueuedEvents.isEmpty()) {
+ MotionEvent event = mQueuedEvents.remove();
+ injectTouchEventData(event);
+ event.recycle();
+ }
+ }
+
+ private void clearQueuedEvents() {
+ while (!mQueuedEvents.isEmpty()) {
+ mQueuedEvents.remove().recycle();
+ }
+ }
+
+ // NOTE: MotionEvents generated from this method should be recycled.
+ private MotionEvent transformToRemoteCoordinates(MotionEvent event) {
+ // Use a copy of the original event so the original event can be passed to other
+ // detectors/handlers in an unmodified state.
+ event = MotionEvent.obtain(event);
+ synchronized (mRenderData) {
+ // Transform the event coordinates so they represent the remote screen coordinates
+ // instead of the local touch display.
+ Matrix inverted = new Matrix();
+ mRenderData.transform.invert(inverted);
+ event.transform(inverted);
+ }
+
+ return event;
+ }
+
+ private void resetStateData() {
+ clearQueuedEvents();
+ mInRemoteGesture = false;
+ mIgnoreTouchEvents = false;
+ }
+}