summaryrefslogtreecommitdiffstats
path: root/remoting
diff options
context:
space:
mode:
authorlambroslambrou@chromium.org <lambroslambrou@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-10-25 16:50:20 +0000
committerlambroslambrou@chromium.org <lambroslambrou@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-10-25 16:50:20 +0000
commitca74711f5188fa335c14d2f0f12aabf163002f0e (patch)
tree0fa9f8bc3bb4d9479173d6201e233b714515aaa4 /remoting
parent86576a61dbe2907b004cc1ac639ba6822264a227 (diff)
downloadchromium_src-ca74711f5188fa335c14d2f0f12aabf163002f0e.zip
chromium_src-ca74711f5188fa335c14d2f0f12aabf163002f0e.tar.gz
chromium_src-ca74711f5188fa335c14d2f0f12aabf163002f0e.tar.bz2
Cursor-tracking in Android Chromoting client.
This is a first step towards implementing the new gestures: * Single-finger swipe moves the cursor (and pans the image in the opposite direction). * Single-finger tap injects left-click. * Two-finger tap injects right-click. * Two-finger pinch to zoom (as before) * Three-finger tap to show the action-bar (as before) There are some regressions (to keep this CL simple) but I plan to address these in follow-up CLs: * No constraints on image-positioning or zoom-level. * Cannot do left-button dragging. * Cannot send middle-button press. * Removed minimum-scroll constraint, as that makes it difficult to control the cursor precisely. BUG=270348 Review URL: https://codereview.chromium.org/26336004 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@231042 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'remoting')
-rw-r--r--remoting/android/java/src/org/chromium/chromoting/Desktop.java2
-rw-r--r--remoting/android/java/src/org/chromium/chromoting/DesktopView.java440
-rw-r--r--remoting/android/java/src/org/chromium/chromoting/DesktopViewInterface.java25
-rw-r--r--remoting/android/java/src/org/chromium/chromoting/RenderData.java28
-rw-r--r--remoting/android/java/src/org/chromium/chromoting/TouchInputHandler.java49
-rw-r--r--remoting/android/java/src/org/chromium/chromoting/TrackingInputHandler.java240
-rw-r--r--remoting/remoting.gyp4
7 files changed, 434 insertions, 354 deletions
diff --git a/remoting/android/java/src/org/chromium/chromoting/Desktop.java b/remoting/android/java/src/org/chromium/chromoting/Desktop.java
index 8a9917d..85e2493 100644
--- a/remoting/android/java/src/org/chromium/chromoting/Desktop.java
+++ b/remoting/android/java/src/org/chromium/chromoting/Desktop.java
@@ -42,7 +42,7 @@ public class Desktop extends Activity {
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
- remoteHostDesktop.requestRecheckConstrainingDimension();
+ remoteHostDesktop.onScreenConfigurationChanged();
}
/** Called to initialize the action bar. */
diff --git a/remoting/android/java/src/org/chromium/chromoting/DesktopView.java b/remoting/android/java/src/org/chromium/chromoting/DesktopView.java
index 8209d28..26cf83a 100644
--- a/remoting/android/java/src/org/chromium/chromoting/DesktopView.java
+++ b/remoting/android/java/src/org/chromium/chromoting/DesktopView.java
@@ -9,16 +9,12 @@ import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
-import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Point;
-import android.os.Bundle;
import android.os.Looper;
import android.text.InputType;
import android.util.Log;
-import android.view.GestureDetector;
import android.view.MotionEvent;
-import android.view.ScaleGestureDetector;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.inputmethod.EditorInfo;
@@ -32,62 +28,16 @@ import org.chromium.chromoting.jni.JniInterface;
* multitouch pan and zoom gestures, and collects and forwards input events.
*/
/** GUI element that holds the drawing canvas. */
-public class DesktopView extends SurfaceView implements Runnable, SurfaceHolder.Callback {
- /**
- * *Square* of the minimum displacement (in pixels) to be recognized as a scroll gesture.
- * Setting this to a lower value forces more frequent canvas redraws during scrolling.
- */
- private static final int MIN_SCROLL_DISTANCE = 8 * 8;
-
- /**
- * Minimum change to the scaling factor to be recognized as a zoom gesture. Setting lower
- * values here will result in more frequent canvas redraws during zooming.
- */
- private static final double MIN_ZOOM_FACTOR = 0.05;
-
- /*
- * These constants must match those in the generated struct protoc::MouseEvent_MouseButton.
- */
- private static final int BUTTON_UNDEFINED = 0;
- private static final int BUTTON_LEFT = 1;
- private static final int BUTTON_RIGHT = 3;
-
- /** Specifies one dimension of an image. */
- private static enum Constraint {
- UNDEFINED, WIDTH, HEIGHT
- }
-
+public class DesktopView extends SurfaceView implements DesktopViewInterface, Runnable,
+ SurfaceHolder.Callback {
+ private RenderData mRenderData;
+ private TouchInputHandler mInputHandler;
private ActionBar mActionBar;
- private GestureDetector mScroller;
- private ScaleGestureDetector mZoomer;
-
- /** Stores pan and zoom configuration and converts image coordinates to screen coordinates. */
- private Matrix mTransform;
-
- private int mScreenWidth;
- private int mScreenHeight;
-
- /**
- * Specifies the position, in image coordinates, at which the cursor image will be drawn.
- * This will normally be at the location of the most recently injected motion event.
- */
- private Point mCursorPosition;
-
- /** Specifies the dimension by which the zoom level is being lower-bounded. */
- private Constraint mConstraint;
-
- /** Whether the dimension of constraint should be reckecked on the next aspect ratio change. */
- private boolean mRecheckConstraint;
-
- /** Whether the right edge of the image was visible on-screen during the last render. */
- private boolean mRightUsedToBeOut;
-
- /** Whether the bottom edge of the image was visible on-screen during the last render. */
- private boolean mBottomUsedToBeOut;
-
- private int mMouseButton;
- private boolean mMousePressed;
+ // Flag to prevent multiple repaint requests from being backed up. Requests for repainting will
+ // be dropped if this is already set to true. This is used by the main thread and the painting
+ // thread, so the access should be synchronized on |mRenderData|.
+ private boolean mRepaintPending;
public DesktopView(Activity context) {
super(context);
@@ -95,26 +45,30 @@ public class DesktopView extends SurfaceView implements Runnable, SurfaceHolder.
// Give this view keyboard focus, allowing us to customize the soft keyboard's settings.
setFocusableInTouchMode(true);
+ mRenderData = new RenderData();
+ mInputHandler = new TrackingInputHandler(this, context, mRenderData);
mActionBar = context.getActionBar();
+ mRepaintPending = false;
getHolder().addCallback(this);
- DesktopListener listener = new DesktopListener();
- mScroller = new GestureDetector(context, listener, null, false);
- mZoomer = new ScaleGestureDetector(context, listener);
-
- mTransform = new Matrix();
- mScreenWidth = 0;
- mScreenHeight = 0;
- mCursorPosition = new Point();
-
- mConstraint = Constraint.UNDEFINED;
- mRecheckConstraint = false;
+ }
- mRightUsedToBeOut = false;
- mBottomUsedToBeOut = false;
+ /**
+ * Request repainting of the desktop view.
+ */
+ void requestRepaint() {
+ synchronized (mRenderData) {
+ if (mRepaintPending) {
+ return;
+ }
+ mRepaintPending = true;
+ }
+ JniInterface.redrawGraphics();
+ }
- mMouseButton = BUTTON_UNDEFINED;
- mMousePressed = false;
+ /** Called whenever the screen configuration is changed. */
+ public void onScreenConfigurationChanged() {
+ mInputHandler.onScreenConfigurationChanged();
}
/**
@@ -129,97 +83,28 @@ public class DesktopView extends SurfaceView implements Runnable, SurfaceHolder.
}
Bitmap image = JniInterface.getVideoFrame();
- Canvas canvas = getHolder().lockCanvas();
- synchronized (mTransform) {
- canvas.setMatrix(mTransform);
-
- // Internal parameters of the transformation matrix.
- float[] values = new float[9];
- mTransform.getValues(values);
-
- // Screen coordinates of two defining points of the image.
- float[] topleft = {0, 0};
- mTransform.mapPoints(topleft);
- float[] bottomright = {image.getWidth(), image.getHeight()};
- mTransform.mapPoints(bottomright);
-
- // Whether to rescale and recenter the view.
- boolean recenter = false;
-
- if (mConstraint == Constraint.UNDEFINED) {
- mConstraint = (double)image.getWidth()/image.getHeight() >
- (double)mScreenWidth/mScreenHeight ? Constraint.WIDTH : Constraint.HEIGHT;
- recenter = true; // We always rescale and recenter after a rotation.
- }
- if (mConstraint == Constraint.WIDTH &&
- ((int)(bottomright[0] - topleft[0] + 0.5) < mScreenWidth || recenter)) {
- // The vertical edges of the image are flush against the device's screen edges
- // when the entire host screen is visible, and the user has zoomed out too far.
- float imageMiddle = (float)image.getHeight() / 2;
- float screenMiddle = (float)mScreenHeight / 2;
- mTransform.setPolyToPoly(
- new float[] {0, imageMiddle, image.getWidth(), imageMiddle}, 0,
- new float[] {0, screenMiddle, mScreenWidth, screenMiddle}, 0, 2);
- } else if (mConstraint == Constraint.HEIGHT &&
- ((int)(bottomright[1] - topleft[1] + 0.5) < mScreenHeight || recenter)) {
- // The horizontal image edges are flush against the device's screen edges when
- // the entire host screen is visible, and the user has zoomed out too far.
- float imageCenter = (float)image.getWidth() / 2;
- float screenCenter = (float)mScreenWidth / 2;
- mTransform.setPolyToPoly(
- new float[] {imageCenter, 0, imageCenter, image.getHeight()}, 0,
- new float[] {screenCenter, 0, screenCenter, mScreenHeight}, 0, 2);
- } else {
- // It's fine for both members of a pair of image edges to be within the screen
- // edges (or "out of bounds"); that simply means that the image is zoomed out as
- // far as permissible. And both members of a pair can obviously be outside the
- // screen's edges, which indicates that the image is zoomed in to far to see the
- // whole host screen. However, if only one of a pair of edges has entered the
- // screen, the user is attempting to scroll into a blank area of the canvas.
-
- // A value of true means the corresponding edge has entered the screen's borders.
- boolean leftEdgeOutOfBounds = values[Matrix.MTRANS_X] > 0;
- boolean topEdgeOutOfBounds = values[Matrix.MTRANS_Y] > 0;
- boolean rightEdgeOutOfBounds = bottomright[0] < mScreenWidth;
- boolean bottomEdgeOutOfBounds = bottomright[1] < mScreenHeight;
-
- // Prevent the user from scrolling past the left or right edge of the image.
- if (leftEdgeOutOfBounds != rightEdgeOutOfBounds) {
- if (leftEdgeOutOfBounds != mRightUsedToBeOut) {
- // Make the left edge of the image flush with the left screen edge.
- values[Matrix.MTRANS_X] = 0;
- }
- else {
- // Make the right edge of the image flush with the right screen edge.
- values[Matrix.MTRANS_X] += mScreenWidth - bottomright[0];
- }
- } else {
- // The else prevents this from being updated during the repositioning process,
- // in which case the view would begin to oscillate.
- mRightUsedToBeOut = rightEdgeOutOfBounds;
- }
-
- // Prevent the user from scrolling past the top or bottom edge of the image.
- if (topEdgeOutOfBounds != bottomEdgeOutOfBounds) {
- if (topEdgeOutOfBounds != mBottomUsedToBeOut) {
- // Make the top edge of the image flush with the top screen edge.
- values[Matrix.MTRANS_Y] = 0;
- } else {
- // Make the bottom edge of the image flush with the bottom screen edge.
- values[Matrix.MTRANS_Y] += mScreenHeight - bottomright[1];
- }
- }
- else {
- // The else prevents this from being updated during the repositioning process,
- // in which case the view would begin to oscillate.
- mBottomUsedToBeOut = bottomEdgeOutOfBounds;
- }
-
- mTransform.setValues(values);
+ int width = image.getWidth();
+ int height = image.getHeight();
+ boolean sizeChanged = false;
+ synchronized (mRenderData) {
+ if (mRenderData.imageWidth != width || mRenderData.imageHeight != height) {
+ // TODO(lambroslambrou): Move this code into a sizeChanged() callback, to be
+ // triggered from JniInterface (on the display thread) when the remote screen size
+ // changes.
+ mRenderData.imageWidth = width;
+ mRenderData.imageHeight = height;
+ sizeChanged = true;
}
+ }
+ if (sizeChanged) {
+ mInputHandler.onHostSizeChanged(width, height);
+ }
- canvas.setMatrix(mTransform);
+ Canvas canvas = getHolder().lockCanvas();
+ synchronized (mRenderData) {
+ mRepaintPending = false;
+ canvas.setMatrix(mRenderData.transform);
}
canvas.drawColor(Color.BLACK);
@@ -228,9 +113,9 @@ public class DesktopView extends SurfaceView implements Runnable, SurfaceHolder.
if (cursorBitmap != null) {
Point hotspot = JniInterface.getCursorHotspot();
int bitmapX, bitmapY;
- synchronized (mCursorPosition) {
- bitmapX = mCursorPosition.x - hotspot.x;
- bitmapY = mCursorPosition.y - hotspot.y;
+ synchronized (mRenderData) {
+ bitmapX = mRenderData.cursorPosition.x - hotspot.x;
+ bitmapY = mRenderData.cursorPosition.y - hotspot.y;
}
canvas.drawBitmap(cursorBitmap, bitmapX, bitmapY, new Paint());
}
@@ -238,43 +123,21 @@ public class DesktopView extends SurfaceView implements Runnable, SurfaceHolder.
}
/**
- * Causes the next canvas redraw to perform a check for which screen dimension more tightly
- * constrains the view of the image. This should be called between the time that a screen size
- * change is requested and the time it actually occurs. If it is not called in such a case, the
- * screen will not be rearranged as aggressively (which is desirable when the software keyboard
- * appears in order to allow it to cover the image without forcing a resize).
- */
- public void requestRecheckConstrainingDimension() {
- mRecheckConstraint = true;
- }
-
- /**
- * Called after the canvas is initially created, then after every
- * subsequent resize, as when the display is rotated.
+ * Called after the canvas is initially created, then after every subsequent resize, as when
+ * the display is rotated.
*/
@Override
- public void surfaceChanged(
- SurfaceHolder holder, int format, int width, int height) {
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
mActionBar.hide();
- synchronized (mTransform) {
- mScreenWidth = width;
- mScreenHeight = height;
-
- if (mRecheckConstraint) {
- mConstraint = Constraint.UNDEFINED;
- mRecheckConstraint = false;
- }
+ synchronized (mRenderData) {
+ mRenderData.screenWidth = width;
+ mRenderData.screenHeight = height;
}
- synchronized (mCursorPosition) {
- mCursorPosition.x = width / 2;
- mCursorPosition.y = height / 2;
- }
+ mInputHandler.onClientSizeChanged(width, height);
- if (!JniInterface.redrawGraphics()) {
- JniInterface.provideRedrawCallback(this);
- }
+ JniInterface.provideRedrawCallback(this);
}
/** Called when the canvas is first created. */
@@ -313,174 +176,53 @@ public class DesktopView extends SurfaceView implements Runnable, SurfaceHolder.
return null;
}
- /** Called when a mouse action is made. */
- private void handleMouseMovement(float x, float y, int button, boolean pressed) {
- float[] coordinates = {x, y};
-
- // Coordinates are relative to the canvas, but we need image coordinates.
- Matrix canvasToImage = new Matrix();
- synchronized (mTransform) {
- mTransform.invert(canvasToImage);
- }
- canvasToImage.mapPoints(coordinates);
-
- int imageX = (int)coordinates[0];
- int imageY = (int)coordinates[1];
-
- synchronized (mCursorPosition) {
- mCursorPosition.x = imageX;
- mCursorPosition.y = imageY;
- }
-
- // Coordinates are now relative to the image, so transmit them to the host.
- JniInterface.mouseAction(imageX, imageY, button, pressed);
-
- // TODO(lambroslambrou): Optimize this to repaint only the areas covered by the old and new
- // cursor positions.
- JniInterface.redrawGraphics();
- }
-
- /**
- * Called whenever the user attempts to touch the canvas. Forwards such
- * events to the appropriate gesture detector until one accepts them.
- */
+ /** Called whenever the user attempts to touch the canvas. */
@Override
public boolean onTouchEvent(MotionEvent event) {
- if (event.getPointerCount() == 3) {
- mActionBar.show();
- }
-
- boolean handled = mScroller.onTouchEvent(event) || mZoomer.onTouchEvent(event);
-
- if (event.getPointerCount() == 1) {
- float x = event.getRawX();
- float y = event.getY();
-
- switch (event.getActionMasked()) {
- case MotionEvent.ACTION_DOWN:
- Log.i("mouse", "Found a finger");
- mMouseButton = BUTTON_UNDEFINED;
- mMousePressed = false;
- break;
-
- case MotionEvent.ACTION_MOVE:
- Log.i("mouse", "Finger is dragging");
- if (mMouseButton == BUTTON_UNDEFINED) {
- Log.i("mouse", "\tStarting left click");
- mMouseButton = BUTTON_LEFT;
- mMousePressed = true;
- }
- break;
-
- case MotionEvent.ACTION_UP:
- Log.i("mouse", "Lost the finger");
- if (mMouseButton == BUTTON_UNDEFINED) {
- // The user pressed and released without moving: do left click and release.
- Log.i("mouse", "\tStarting and finishing left click");
- handleMouseMovement(x, y, BUTTON_LEFT, true);
- mMouseButton = BUTTON_LEFT;
- mMousePressed = false;
- }
- else if (mMousePressed) {
- Log.i("mouse", "\tReleasing the currently-pressed button");
- mMousePressed = false;
- }
- else {
- Log.w("mouse", "Button already in released state before gesture ended");
- }
- break;
-
- default:
- return handled;
- }
- handleMouseMovement(x, y, mMouseButton, mMousePressed);
-
- return true;
- }
-
- return handled;
+ return mInputHandler.onTouchEvent(event);
}
- /** Responds to touch events filtered by the gesture detectors. */
- private class DesktopListener extends GestureDetector.SimpleOnGestureListener
- implements ScaleGestureDetector.OnScaleGestureListener {
- /**
- * Called when the user is scrolling. We refuse to accept or process the event unless it
- * is being performed with 2 or more touch points, in order to reserve single-point touch
- * events for emulating mouse input.
- */
- @Override
- public boolean onScroll(
- MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
- if (e2.getPointerCount() < 2 ||
- Math.pow(distanceX, 2) + Math.pow(distanceY, 2) < MIN_SCROLL_DISTANCE) {
- return false;
- }
-
- synchronized (mTransform) {
- mTransform.postTranslate(-distanceX, -distanceY);
- }
- JniInterface.redrawGraphics();
- return true;
- }
-
- /** Called when the user is in the process of pinch-zooming. */
- @Override
- public boolean onScale(ScaleGestureDetector detector) {
- if (Math.abs(detector.getScaleFactor() - 1) < MIN_ZOOM_FACTOR) {
- return false;
+ @Override
+ public void injectMouseEvent(int x, int y, int button, boolean pressed) {
+ boolean cursorMoved = false;
+ synchronized (mRenderData) {
+ // Test if the cursor actually moved, which requires repainting the cursor. This
+ // requires that the TouchInputHandler doesn't mutate |mRenderData.cursorPosition|
+ // directly.
+ if (x != mRenderData.cursorPosition.x) {
+ mRenderData.cursorPosition.x = x;
+ cursorMoved = true;
}
-
- synchronized (mTransform) {
- float scaleFactor = detector.getScaleFactor();
- mTransform.postScale(
- scaleFactor, scaleFactor, detector.getFocusX(), detector.getFocusY());
+ if (y != mRenderData.cursorPosition.y) {
+ mRenderData.cursorPosition.y = y;
+ cursorMoved = true;
}
- JniInterface.redrawGraphics();
- return true;
}
- /** Called whenever a gesture starts. Always accepts the gesture so it isn't ignored. */
- @Override
- public boolean onDown(MotionEvent e) {
- return true;
+ if (button == TouchInputHandler.BUTTON_UNDEFINED && !cursorMoved) {
+ // No need to inject anything or repaint.
+ return;
}
- /**
- * Called when the user starts to zoom. Always accepts the zoom so that
- * onScale() can decide whether to respond to it.
- */
- @Override
- public boolean onScaleBegin(ScaleGestureDetector detector) {
- return true;
+ JniInterface.mouseAction(x, y, button, pressed);
+ if (cursorMoved) {
+ // TODO(lambroslambrou): Optimize this by only repainting the affected areas.
+ requestRepaint();
}
+ }
- /** Called when the user is done zooming. Defers to onScale()'s judgement. */
- @Override
- public void onScaleEnd(ScaleGestureDetector detector) {
- onScale(detector);
- }
-
- /** Called when the user holds down on the screen. Starts a right-click. */
- @Override
- public void onLongPress(MotionEvent e) {
- if (e.getPointerCount() > 1) {
- return;
- }
-
- float x = e.getRawX();
- float y = e.getY();
+ @Override
+ public void showActionBar() {
+ mActionBar.show();
+ }
- Log.i("mouse", "Finger held down");
- if (mMousePressed) {
- Log.i("mouse", "\tReleasing the currently-pressed button");
- handleMouseMovement(x, y, mMouseButton, false);
- }
+ @Override
+ public void showKeyboard() {
+ // TODO(lambroslambrou): Implement this.
+ }
- Log.i("mouse", "\tStarting right click");
- mMouseButton = BUTTON_RIGHT;
- mMousePressed = true;
- handleMouseMovement(x, y, mMouseButton, mMousePressed);
- }
+ @Override
+ public void transformationChanged() {
+ requestRepaint();
}
}
diff --git a/remoting/android/java/src/org/chromium/chromoting/DesktopViewInterface.java b/remoting/android/java/src/org/chromium/chromoting/DesktopViewInterface.java
new file mode 100644
index 0000000..ba9e1e6
--- /dev/null
+++ b/remoting/android/java/src/org/chromium/chromoting/DesktopViewInterface.java
@@ -0,0 +1,25 @@
+// Copyright 2013 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;
+
+/**
+ * Callback interface to allow the TouchInputHandler to request actions on the DesktopView.
+ */
+interface DesktopViewInterface {
+ /** Inject a mouse-move event, with optional button press/release. */
+ public void injectMouseEvent(int x, int y, int button, boolean pressed);
+
+ /** Shows the action bar. */
+ public void showActionBar();
+
+ /** Shows the software keyboard. */
+ public void showKeyboard();
+
+ /**
+ * Informs the view that its transformation matrix (for rendering the remote desktop bitmap)
+ * has been changed by the TouchInputHandler, which requires repainting.
+ */
+ public void transformationChanged();
+}
diff --git a/remoting/android/java/src/org/chromium/chromoting/RenderData.java b/remoting/android/java/src/org/chromium/chromoting/RenderData.java
new file mode 100644
index 0000000..f161c9f
--- /dev/null
+++ b/remoting/android/java/src/org/chromium/chromoting/RenderData.java
@@ -0,0 +1,28 @@
+// Copyright 2013 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.graphics.Point;
+
+/**
+ * This class stores data that needs to be accessed on both the display thread and the
+ * event-processing thread.
+ */
+public class RenderData {
+ /** Stores pan and zoom configuration and converts image coordinates to screen coordinates. */
+ public Matrix transform = new Matrix();
+
+ public int screenWidth = 0;
+ public int screenHeight = 0;
+ public int imageWidth = 0;
+ public int imageHeight = 0;
+
+ /**
+ * Specifies the position, in image coordinates, at which the cursor image will be drawn.
+ * This will normally be at the location of the most recently injected motion event.
+ */
+ public Point cursorPosition = new Point();
+}
diff --git a/remoting/android/java/src/org/chromium/chromoting/TouchInputHandler.java b/remoting/android/java/src/org/chromium/chromoting/TouchInputHandler.java
new file mode 100644
index 0000000..85578b6
--- /dev/null
+++ b/remoting/android/java/src/org/chromium/chromoting/TouchInputHandler.java
@@ -0,0 +1,49 @@
+// Copyright 2013 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.view.MotionEvent;
+
+/**
+ * This interface allows multiple styles of touchscreen UI to be implemented and dynamically
+ * switched. The DesktopView passes the low-level touchscreen events and other events via this
+ * interface. The implementation recognizes and processes the touchscreen gestures, and then
+ * performs actions on the DesktopView (such as panning/zooming the display, injecting input, or
+ * showing/hiding UI elements).
+ */
+interface TouchInputHandler {
+ // These constants must match those in the generated struct protoc::MouseEvent_MouseButton.
+ public static final int BUTTON_UNDEFINED = 0;
+ public static final int BUTTON_LEFT = 1;
+ public static final int BUTTON_MIDDLE = 2;
+ public static final int BUTTON_RIGHT = 3;
+
+ /**
+ * Processes a touch event. This should be called by the View in its onTouchEvent() handler.
+ */
+ boolean onTouchEvent(MotionEvent event);
+
+ /**
+ * Called when the screen configuration is changed, such as when the screen is rotated or an
+ * external display is plugged in. This is not called if the client display area changes as a
+ * result of showing/hiding UI elements such as a keyboard. For example, an implementation
+ * could set a flag to reset the zoom level when the screen is rotated, but not when the
+ * software keyboard appears. After this is called, the onClientSizeChanged() method will
+ * shortly be called with the dimensions of the new client display area.
+ */
+ void onScreenConfigurationChanged();
+
+ /**
+ * Called whenever the client display area changes size. The caller will handle repainting
+ * after this method returns.
+ */
+ void onClientSizeChanged(int width, int height);
+
+ /**
+ * Called when the host screen size is changed. The caller will handle repainting after this
+ * method returns.
+ */
+ void onHostSizeChanged(int width, int height);
+}
diff --git a/remoting/android/java/src/org/chromium/chromoting/TrackingInputHandler.java b/remoting/android/java/src/org/chromium/chromoting/TrackingInputHandler.java
new file mode 100644
index 0000000..30b66cc
--- /dev/null
+++ b/remoting/android/java/src/org/chromium/chromoting/TrackingInputHandler.java
@@ -0,0 +1,240 @@
+// Copyright 2013 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.content.Context;
+import android.graphics.Matrix;
+import android.graphics.PointF;
+import android.util.Log;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
+
+/**
+ * This class implements the cursor-tracking behavior and gestures.
+ */
+public class TrackingInputHandler implements TouchInputHandler {
+ /**
+ * Minimum change to the scaling factor to be recognized as a zoom gesture. Setting lower
+ * values here will result in more frequent canvas redraws during zooming.
+ */
+ private static final double MIN_ZOOM_DELTA = 0.05;
+
+ private DesktopViewInterface mViewer;
+ private RenderData mRenderData;
+
+ private GestureDetector mScroller;
+ private ScaleGestureDetector mZoomer;
+
+ /**
+ * The current cursor position is stored here as floats, so that the desktop image can be
+ * positioned with sub-pixel accuracy, to give a smoother panning animation at high zoom levels.
+ */
+ private PointF mCursorPosition;
+
+ private int mMouseButton;
+
+ /**
+ * Distinguish between finger tap and swipe. One-finger down then up should inject a
+ * left-click event. But if the finger is dragged before being released, this should move
+ * the cursor without injecting any button event. This flag is set when a motion event is
+ * detected.
+ */
+ private boolean mFingerMoved = false;
+
+ public TrackingInputHandler(DesktopViewInterface viewer, Context context,
+ RenderData renderData) {
+ mViewer = viewer;
+ mRenderData = renderData;
+
+ GestureListener listener = new GestureListener();
+ mScroller = new GestureDetector(context, listener, null, false);
+
+ // If long-press is enabled, the gesture-detector will not emit any further onScroll
+ // notifications after the onLongPress notification. Since onScroll is being used for
+ // moving the cursor, it means that the cursor would become stuck if the finger were held
+ // down too long.
+ mScroller.setIsLongpressEnabled(false);
+
+ mZoomer = new ScaleGestureDetector(context, listener);
+
+ mCursorPosition = new PointF();
+
+ mMouseButton = BUTTON_UNDEFINED;
+ mFingerMoved = false;
+ }
+
+ /**
+ * Moves the mouse-cursor, injects a mouse-move event and repositions the image.
+ */
+ private void moveCursor(float newX, float newY) {
+ synchronized (mRenderData) {
+ // Constrain cursor to the image area.
+ if (newX < 0) newX = 0;
+ if (newY < 0) newY = 0;
+ if (newX > mRenderData.imageWidth) newX = mRenderData.imageWidth;
+ if (newY > mRenderData.imageHeight) newY = mRenderData.imageHeight;
+ mCursorPosition.set(newX, newY);
+ repositionImage();
+ }
+
+ mViewer.injectMouseEvent((int)newX, (int)newY, BUTTON_UNDEFINED, false);
+ }
+
+ /**
+ * Repositions the image by translating it (without affecting the zoom level) to place the
+ * cursor close to the center of the screen.
+ */
+ private void repositionImage() {
+ synchronized (mRenderData) {
+ // Get the current cursor position in screen coordinates.
+ float[] cursorScreen = {mCursorPosition.x, mCursorPosition.y};
+ mRenderData.transform.mapPoints(cursorScreen);
+
+ // Translate so the cursor is displayed in the middle of the screen.
+ mRenderData.transform.postTranslate(
+ (float)mRenderData.screenWidth / 2 - cursorScreen[0],
+ (float)mRenderData.screenHeight / 2 - cursorScreen[1]);
+ }
+ mViewer.transformationChanged();
+ }
+
+ /** Injects a button event using the current cursor location. */
+ private void injectButtonEvent(int button, boolean pressed) {
+ mViewer.injectMouseEvent((int)mCursorPosition.x, (int)mCursorPosition.y, button, pressed);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ // Avoid short-circuit logic evaluation - ensure both gesture detectors see all events so
+ // that they generate correct notifications.
+ boolean handled = mScroller.onTouchEvent(event);
+ handled = mZoomer.onTouchEvent(event) || handled;
+
+ int pointerCount = event.getPointerCount();
+
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ case MotionEvent.ACTION_POINTER_DOWN:
+ switch (pointerCount) {
+ case 1:
+ mFingerMoved = false;
+ mMouseButton = BUTTON_LEFT;
+ break;
+ case 2:
+ mMouseButton = BUTTON_RIGHT;
+ break;
+ case 3:
+ mMouseButton = BUTTON_UNDEFINED;
+ // TODO(lambroslambrou): Add 3-finger-tap for middle-click, and use 3-finger
+ // swipe to show the action-bar or keyboard.
+ break;
+ default:
+ break;
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ if (mFingerMoved) {
+ // Don't inject anything.
+ mFingerMoved = false;
+ } else {
+ // The user pressed and released without moving. Inject a click event for a
+ // mouse button according to how many fingers were used.
+ injectButtonEvent(mMouseButton, true);
+ injectButtonEvent(mMouseButton, false);
+ handled = true;
+ }
+ break;
+ case MotionEvent.ACTION_POINTER_UP:
+ // |pointerCount| is the number of fingers that were on the screen prior to the UP
+ // event.
+ if (pointerCount == 3) {
+ mViewer.showActionBar();
+ handled = true;
+ }
+ break;
+ default:
+ break;
+ }
+ return handled;
+ }
+
+ @Override
+ public void onScreenConfigurationChanged() {
+ }
+
+ @Override
+ public void onClientSizeChanged(int width, int height) {
+ repositionImage();
+ }
+
+ @Override
+ public void onHostSizeChanged(int width, int height) {
+ moveCursor((float)width / 2, (float)height / 2);
+ }
+
+ /** Responds to touch events filtered by the gesture detectors. */
+ private class GestureListener extends GestureDetector.SimpleOnGestureListener
+ implements ScaleGestureDetector.OnScaleGestureListener {
+ /**
+ * Called when the user drags one or more fingers across the touchscreen.
+ */
+ @Override
+ public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
+ if (e2.getPointerCount() != 1) {
+ return false;
+ }
+ mFingerMoved = true;
+
+ float[] delta = {distanceX, distanceY};
+ synchronized (mRenderData) {
+ Matrix canvasToImage = new Matrix();
+ mRenderData.transform.invert(canvasToImage);
+ canvasToImage.mapVectors(delta);
+ }
+
+ moveCursor(mCursorPosition.x - delta[0], mCursorPosition.y - delta[1]);
+ return true;
+ }
+
+ /** Called when the user is in the process of pinch-zooming. */
+ @Override
+ public boolean onScale(ScaleGestureDetector detector) {
+ if (Math.abs(detector.getScaleFactor() - 1) < MIN_ZOOM_DELTA) {
+ return false;
+ }
+ mFingerMoved = true;
+
+ float scaleFactor = detector.getScaleFactor();
+ synchronized (mRenderData) {
+ mRenderData.transform.postScale(
+ scaleFactor, scaleFactor, detector.getFocusX(), detector.getFocusY());
+ }
+ repositionImage();
+ return true;
+ }
+
+ /** Called whenever a gesture starts. Always accepts the gesture so it isn't ignored. */
+ @Override
+ public boolean onDown(MotionEvent e) {
+ return true;
+ }
+
+ /**
+ * Called when the user starts to zoom. Always accepts the zoom so that
+ * onScale() can decide whether to respond to it.
+ */
+ @Override
+ public boolean onScaleBegin(ScaleGestureDetector detector) {
+ return true;
+ }
+
+ /** Called when the user is done zooming. Defers to onScale()'s judgement. */
+ @Override
+ public void onScaleEnd(ScaleGestureDetector detector) {
+ onScale(detector);
+ }
+ }
+}
diff --git a/remoting/remoting.gyp b/remoting/remoting.gyp
index 0567506..aa20474 100644
--- a/remoting/remoting.gyp
+++ b/remoting/remoting.gyp
@@ -1894,10 +1894,6 @@
'java_in_dir': 'android/java',
'additional_res_dirs': [ '<(SHARED_INTERMEDIATE_DIR)/remoting/android/res' ],
'additional_input_paths': [
- 'android/java/src/org/chromium/chromoting/Chromoting.java',
- 'android/java/src/org/chromium/chromoting/Desktop.java',
- 'android/java/src/org/chromium/chromoting/DesktopView.java',
- 'android/java/src/org/chromium/chromoting/jni/JniInterface.java',
'<(PRODUCT_DIR)/obj/remoting/remoting_android_resources.actions_rules_copies.stamp',
],
},