| Index: remoting/android/java/src/org/chromium/chromoting/DesktopView.java | 
| diff --git a/remoting/android/java/src/org/chromium/chromoting/DesktopView.java b/remoting/android/java/src/org/chromium/chromoting/DesktopView.java | 
| index b7666b303364faa325f5c534c1f1aab3c4edac87..1d015106f8bd121d4b085b0f2c00f32f954c2801 100644 | 
| --- a/remoting/android/java/src/org/chromium/chromoting/DesktopView.java | 
| +++ b/remoting/android/java/src/org/chromium/chromoting/DesktopView.java | 
| @@ -32,13 +32,20 @@ public class DesktopView extends SurfaceView implements Runnable, SurfaceHolder. | 
| * *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 int MIN_SCROLL_DISTANCE = 8 * 8; | 
| +    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 double MIN_ZOOM_FACTOR = 0.05; | 
| +    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 { | 
| @@ -63,6 +70,9 @@ public class DesktopView extends SurfaceView implements Runnable, SurfaceHolder. | 
| /** Whether the bottom edge of the image was visible on-screen during the last render. */ | 
| private boolean mBottomUsedToBeOut; | 
|  | 
| +    private int mMouseButton; | 
| +    private boolean mMousePressed; | 
| + | 
| /** Whether the device has just been rotated, necessitating a canvas redraw. */ | 
| private boolean mJustRotated; | 
|  | 
| @@ -81,6 +91,9 @@ public class DesktopView extends SurfaceView implements Runnable, SurfaceHolder. | 
| mRightUsedToBeOut = false; | 
| mBottomUsedToBeOut = false; | 
|  | 
| +        mMouseButton = BUTTON_UNDEFINED; | 
| +        mMousePressed = false; | 
| + | 
| mJustRotated = false; | 
| } | 
|  | 
| @@ -151,27 +164,37 @@ public class DesktopView extends SurfaceView implements Runnable, SurfaceHolder. | 
| 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 view would oscillate if this were updated while scrolling off-screen. | 
| +                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 view would oscillate if this were updated while scrolling off-screen. | 
| +                else { | 
| +                    // The else prevents this from being updated during the repositioning process, | 
| +                    // in which case the view would begin to oscillate. | 
| mBottomUsedToBeOut = bottomEdgeOutOfBounds; | 
| } | 
|  | 
| @@ -224,13 +247,71 @@ public class DesktopView extends SurfaceView implements Runnable, SurfaceHolder. | 
| Log.i("deskview", "DesktopView.surfaceDestroyed(...)"); | 
| } | 
|  | 
| +    /** Called when a mouse action is made. */ | 
| +    private void handleMouseMovement(float[] coordinates, int button, boolean pressed) { | 
| +        // Coordinates are relative to the canvas, but we need image coordinates. | 
| +        Matrix canvasToImage = new Matrix(); | 
| +        mTransform.invert(canvasToImage); | 
| +        canvasToImage.mapPoints(coordinates); | 
| + | 
| +        // Coordinates are now relative to the image, so transmit them to the host. | 
| +        JniInterface.mouseAction((int)coordinates[0], (int)coordinates[1], button, pressed); | 
| +    } | 
| + | 
| /** | 
| * Called whenever the user attempts to touch the canvas. Forwards such | 
| * events to the appropriate gesture detector until one accepts them. | 
| */ | 
| @Override | 
| public boolean onTouchEvent(MotionEvent event) { | 
| -        return mScroller.onTouchEvent(event) || mZoomer.onTouchEvent(event); | 
| +        boolean handled = mScroller.onTouchEvent(event) || mZoomer.onTouchEvent(event); | 
| + | 
| +        if (event.getPointerCount()==1) { | 
| +            float[] coordinates = {event.getRawX(), 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(coordinates, 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(coordinates, mMouseButton, mMousePressed); | 
| + | 
| +            return true; | 
| +        } | 
| + | 
| +        return handled; | 
| } | 
|  | 
| /** Responds to touch events filtered by the gesture detectors. */ | 
| @@ -272,6 +353,12 @@ public class DesktopView extends SurfaceView implements Runnable, SurfaceHolder. | 
| 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. | 
| @@ -281,12 +368,31 @@ public class DesktopView extends SurfaceView implements Runnable, SurfaceHolder. | 
| return true; | 
| } | 
|  | 
| -        /** | 
| -         * Called when the user is done zooming. Defers to onScale()'s judgement. | 
| -         */ | 
| +        /** 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[] coordinates = new float[] {e.getRawX(), e.getY()}; | 
| + | 
| +            Log.i("mouse", "Finger held down"); | 
| +            if (mMousePressed) { | 
| +                Log.i("mouse", "\tReleasing the currently-pressed button"); | 
| +                handleMouseMovement(coordinates, mMouseButton, false); | 
| +            } | 
| + | 
| +            Log.i("mouse", "\tStarting right click"); | 
| +            mMouseButton = BUTTON_RIGHT; | 
| +            mMousePressed = true; | 
| +            handleMouseMovement(coordinates, mMouseButton, mMousePressed); | 
| +        } | 
| } | 
| } | 
|  |