| OLD | NEW |
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. | 1 // Copyright 2013 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 package org.chromium.chromoting; | 5 package org.chromium.chromoting; |
| 6 | 6 |
| 7 import android.content.Context; | 7 import android.content.Context; |
| 8 import android.graphics.Bitmap; | 8 import android.graphics.Bitmap; |
| 9 import android.graphics.Canvas; | 9 import android.graphics.Canvas; |
| 10 import android.graphics.Color; | 10 import android.graphics.Color; |
| (...skipping 14 matching lines...) Expand all Loading... |
| 25 * The user interface for viewing and interacting with a specific remote host. | 25 * The user interface for viewing and interacting with a specific remote host. |
| 26 * It provides a canvas onto which the video feed is rendered, handles | 26 * It provides a canvas onto which the video feed is rendered, handles |
| 27 * multitouch pan and zoom gestures, and collects and forwards input events. | 27 * multitouch pan and zoom gestures, and collects and forwards input events. |
| 28 */ | 28 */ |
| 29 /** GUI element that holds the drawing canvas. */ | 29 /** GUI element that holds the drawing canvas. */ |
| 30 public class DesktopView extends SurfaceView implements Runnable, SurfaceHolder.
Callback { | 30 public class DesktopView extends SurfaceView implements Runnable, SurfaceHolder.
Callback { |
| 31 /** | 31 /** |
| 32 * *Square* of the minimum displacement (in pixels) to be recognized as a sc
roll gesture. | 32 * *Square* of the minimum displacement (in pixels) to be recognized as a sc
roll gesture. |
| 33 * Setting this to a lower value forces more frequent canvas redraws during
scrolling. | 33 * Setting this to a lower value forces more frequent canvas redraws during
scrolling. |
| 34 */ | 34 */ |
| 35 private static int MIN_SCROLL_DISTANCE = 8 * 8; | 35 private static final int MIN_SCROLL_DISTANCE = 8 * 8; |
| 36 | 36 |
| 37 /** | 37 /** |
| 38 * Minimum change to the scaling factor to be recognized as a zoom gesture.
Setting lower | 38 * Minimum change to the scaling factor to be recognized as a zoom gesture.
Setting lower |
| 39 * values here will result in more frequent canvas redraws during zooming. | 39 * values here will result in more frequent canvas redraws during zooming. |
| 40 */ | 40 */ |
| 41 private static double MIN_ZOOM_FACTOR = 0.05; | 41 private static final double MIN_ZOOM_FACTOR = 0.05; |
| 42 |
| 43 /* |
| 44 * These constants must match those in the generated struct protoc::MouseEve
nt_MouseButton. |
| 45 */ |
| 46 private static final int BUTTON_UNDEFINED = 0; |
| 47 private static final int BUTTON_LEFT = 1; |
| 48 private static final int BUTTON_RIGHT = 3; |
| 42 | 49 |
| 43 /** Specifies one dimension of an image. */ | 50 /** Specifies one dimension of an image. */ |
| 44 private static enum Constraint { | 51 private static enum Constraint { |
| 45 UNDEFINED, WIDTH, HEIGHT | 52 UNDEFINED, WIDTH, HEIGHT |
| 46 } | 53 } |
| 47 | 54 |
| 48 private GestureDetector mScroller; | 55 private GestureDetector mScroller; |
| 49 private ScaleGestureDetector mZoomer; | 56 private ScaleGestureDetector mZoomer; |
| 50 | 57 |
| 51 /** Stores pan and zoom configuration and converts image coordinates to scre
en coordinates. */ | 58 /** Stores pan and zoom configuration and converts image coordinates to scre
en coordinates. */ |
| 52 private Matrix mTransform; | 59 private Matrix mTransform; |
| 53 | 60 |
| 54 private int mScreenWidth; | 61 private int mScreenWidth; |
| 55 private int mScreenHeight; | 62 private int mScreenHeight; |
| 56 | 63 |
| 57 /** Specifies the dimension by which the zoom level is being lower-bounded.
*/ | 64 /** Specifies the dimension by which the zoom level is being lower-bounded.
*/ |
| 58 private Constraint mConstraint; | 65 private Constraint mConstraint; |
| 59 | 66 |
| 60 /** Whether the right edge of the image was visible on-screen during the las
t render. */ | 67 /** Whether the right edge of the image was visible on-screen during the las
t render. */ |
| 61 private boolean mRightUsedToBeOut; | 68 private boolean mRightUsedToBeOut; |
| 62 | 69 |
| 63 /** Whether the bottom edge of the image was visible on-screen during the la
st render. */ | 70 /** Whether the bottom edge of the image was visible on-screen during the la
st render. */ |
| 64 private boolean mBottomUsedToBeOut; | 71 private boolean mBottomUsedToBeOut; |
| 65 | 72 |
| 73 private int mMouseButton; |
| 74 private boolean mMousePressed; |
| 75 |
| 66 /** Whether the device has just been rotated, necessitating a canvas redraw.
*/ | 76 /** Whether the device has just been rotated, necessitating a canvas redraw.
*/ |
| 67 private boolean mJustRotated; | 77 private boolean mJustRotated; |
| 68 | 78 |
| 69 public DesktopView(Context context) { | 79 public DesktopView(Context context) { |
| 70 super(context); | 80 super(context); |
| 71 getHolder().addCallback(this); | 81 getHolder().addCallback(this); |
| 72 DesktopListener listener = new DesktopListener(); | 82 DesktopListener listener = new DesktopListener(); |
| 73 mScroller = new GestureDetector(context, listener); | 83 mScroller = new GestureDetector(context, listener); |
| 74 mZoomer = new ScaleGestureDetector(context, listener); | 84 mZoomer = new ScaleGestureDetector(context, listener); |
| 75 | 85 |
| 76 mTransform = new Matrix(); | 86 mTransform = new Matrix(); |
| 77 mScreenWidth = 0; | 87 mScreenWidth = 0; |
| 78 mScreenHeight = 0; | 88 mScreenHeight = 0; |
| 79 mConstraint = Constraint.UNDEFINED; | 89 mConstraint = Constraint.UNDEFINED; |
| 80 | 90 |
| 81 mRightUsedToBeOut = false; | 91 mRightUsedToBeOut = false; |
| 82 mBottomUsedToBeOut = false; | 92 mBottomUsedToBeOut = false; |
| 83 | 93 |
| 94 mMouseButton = BUTTON_UNDEFINED; |
| 95 mMousePressed = false; |
| 96 |
| 84 mJustRotated = false; | 97 mJustRotated = false; |
| 85 } | 98 } |
| 86 | 99 |
| 87 /** | 100 /** |
| 88 * Redraws the canvas. This should be done on a non-UI thread or it could | 101 * Redraws the canvas. This should be done on a non-UI thread or it could |
| 89 * cause the UI to lag. Specifically, it is currently invoked on the native | 102 * cause the UI to lag. Specifically, it is currently invoked on the native |
| 90 * graphics thread using a JNI. | 103 * graphics thread using a JNI. |
| 91 */ | 104 */ |
| 92 @Override | 105 @Override |
| 93 public void run() { | 106 public void run() { |
| (...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 144 // screen's edges, which indicates that the image is zoomed in t
o far to see the | 157 // screen's edges, which indicates that the image is zoomed in t
o far to see the |
| 145 // whole host screen. However, if only one of a pair of edges ha
s entered the | 158 // whole host screen. However, if only one of a pair of edges ha
s entered the |
| 146 // screen, the user is attempting to scroll into a blank area of
the canvas. | 159 // screen, the user is attempting to scroll into a blank area of
the canvas. |
| 147 | 160 |
| 148 // A value of true means the corresponding edge has entered the
screen's borders. | 161 // A value of true means the corresponding edge has entered the
screen's borders. |
| 149 boolean leftEdgeOutOfBounds = values[Matrix.MTRANS_X] > 0; | 162 boolean leftEdgeOutOfBounds = values[Matrix.MTRANS_X] > 0; |
| 150 boolean topEdgeOutOfBounds = values[Matrix.MTRANS_Y] > 0; | 163 boolean topEdgeOutOfBounds = values[Matrix.MTRANS_Y] > 0; |
| 151 boolean rightEdgeOutOfBounds = bottomright[0] < mScreenWidth; | 164 boolean rightEdgeOutOfBounds = bottomright[0] < mScreenWidth; |
| 152 boolean bottomEdgeOutOfBounds = bottomright[1] < mScreenHeight; | 165 boolean bottomEdgeOutOfBounds = bottomright[1] < mScreenHeight; |
| 153 | 166 |
| 167 // Prevent the user from scrolling past the left or right edge o
f the image. |
| 154 if (leftEdgeOutOfBounds != rightEdgeOutOfBounds) { | 168 if (leftEdgeOutOfBounds != rightEdgeOutOfBounds) { |
| 155 if (leftEdgeOutOfBounds != mRightUsedToBeOut) { | 169 if (leftEdgeOutOfBounds != mRightUsedToBeOut) { |
| 170 // Make the left edge of the image flush with the left s
creen edge. |
| 156 values[Matrix.MTRANS_X] = 0; | 171 values[Matrix.MTRANS_X] = 0; |
| 157 } | 172 } |
| 158 else { | 173 else { |
| 174 // Make the right edge of the image flush with the right
screen edge. |
| 159 values[Matrix.MTRANS_X] += mScreenWidth - bottomright[0]
; | 175 values[Matrix.MTRANS_X] += mScreenWidth - bottomright[0]
; |
| 160 } | 176 } |
| 161 } | 177 } |
| 162 else { // The view would oscillate if this were updated while s
crolling off-screen. | 178 else { |
| 179 // The else prevents this from being updated during the repo
sitioning process, |
| 180 // in which case the view would begin to oscillate. |
| 163 mRightUsedToBeOut = rightEdgeOutOfBounds; | 181 mRightUsedToBeOut = rightEdgeOutOfBounds; |
| 164 } | 182 } |
| 165 | 183 |
| 184 // Prevent the user from scrolling past the top or bottom edge o
f the image. |
| 166 if (topEdgeOutOfBounds != bottomEdgeOutOfBounds) { | 185 if (topEdgeOutOfBounds != bottomEdgeOutOfBounds) { |
| 167 if (topEdgeOutOfBounds != mBottomUsedToBeOut) { | 186 if (topEdgeOutOfBounds != mBottomUsedToBeOut) { |
| 187 // Make the top edge of the image flush with the top scr
een edge. |
| 168 values[Matrix.MTRANS_Y] = 0; | 188 values[Matrix.MTRANS_Y] = 0; |
| 169 } | 189 } |
| 170 else { | 190 else { |
| 191 // Make the bottom edge of the image flush with the bott
om screen edge. |
| 171 values[Matrix.MTRANS_Y] += mScreenHeight - bottomright[1
]; | 192 values[Matrix.MTRANS_Y] += mScreenHeight - bottomright[1
]; |
| 172 } | 193 } |
| 173 } | 194 } |
| 174 else { // The view would oscillate if this were updated while s
crolling off-screen. | 195 else { |
| 196 // The else prevents this from being updated during the repo
sitioning process, |
| 197 // in which case the view would begin to oscillate. |
| 175 mBottomUsedToBeOut = bottomEdgeOutOfBounds; | 198 mBottomUsedToBeOut = bottomEdgeOutOfBounds; |
| 176 } | 199 } |
| 177 | 200 |
| 178 mTransform.setValues(values); | 201 mTransform.setValues(values); |
| 179 } | 202 } |
| 180 | 203 |
| 181 canvas.setMatrix(mTransform); | 204 canvas.setMatrix(mTransform); |
| 182 } | 205 } |
| 183 | 206 |
| 184 canvas.drawColor(Color.BLACK); | 207 canvas.drawColor(Color.BLACK); |
| (...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 217 Log.i("deskview", "DesktopView.surfaceCreated(...)"); | 240 Log.i("deskview", "DesktopView.surfaceCreated(...)"); |
| 218 JniInterface.provideRedrawCallback(this); | 241 JniInterface.provideRedrawCallback(this); |
| 219 } | 242 } |
| 220 | 243 |
| 221 /** Called when the canvas is finally destroyed. */ | 244 /** Called when the canvas is finally destroyed. */ |
| 222 @Override | 245 @Override |
| 223 public void surfaceDestroyed(SurfaceHolder holder) { | 246 public void surfaceDestroyed(SurfaceHolder holder) { |
| 224 Log.i("deskview", "DesktopView.surfaceDestroyed(...)"); | 247 Log.i("deskview", "DesktopView.surfaceDestroyed(...)"); |
| 225 } | 248 } |
| 226 | 249 |
| 250 /** Called when a mouse action is made. */ |
| 251 private void handleMouseMovement(float[] coordinates, int button, boolean pr
essed) { |
| 252 // Coordinates are relative to the canvas, but we need image coordinates
. |
| 253 Matrix canvasToImage = new Matrix(); |
| 254 mTransform.invert(canvasToImage); |
| 255 canvasToImage.mapPoints(coordinates); |
| 256 |
| 257 // Coordinates are now relative to the image, so transmit them to the ho
st. |
| 258 JniInterface.mouseAction((int)coordinates[0], (int)coordinates[1], butto
n, pressed); |
| 259 } |
| 260 |
| 227 /** | 261 /** |
| 228 * Called whenever the user attempts to touch the canvas. Forwards such | 262 * Called whenever the user attempts to touch the canvas. Forwards such |
| 229 * events to the appropriate gesture detector until one accepts them. | 263 * events to the appropriate gesture detector until one accepts them. |
| 230 */ | 264 */ |
| 231 @Override | 265 @Override |
| 232 public boolean onTouchEvent(MotionEvent event) { | 266 public boolean onTouchEvent(MotionEvent event) { |
| 233 return mScroller.onTouchEvent(event) || mZoomer.onTouchEvent(event); | 267 boolean handled = mScroller.onTouchEvent(event) || mZoomer.onTouchEvent(
event); |
| 268 |
| 269 if (event.getPointerCount()==1) { |
| 270 float[] coordinates = {event.getRawX(), event.getY()}; |
| 271 |
| 272 switch (event.getActionMasked()) { |
| 273 case MotionEvent.ACTION_DOWN: |
| 274 Log.i("mouse", "Found a finger"); |
| 275 mMouseButton = BUTTON_UNDEFINED; |
| 276 mMousePressed = false; |
| 277 break; |
| 278 |
| 279 case MotionEvent.ACTION_MOVE: |
| 280 Log.i("mouse", "Finger is dragging"); |
| 281 if (mMouseButton == BUTTON_UNDEFINED) { |
| 282 Log.i("mouse", "\tStarting left click"); |
| 283 mMouseButton = BUTTON_LEFT; |
| 284 mMousePressed = true; |
| 285 } |
| 286 break; |
| 287 |
| 288 case MotionEvent.ACTION_UP: |
| 289 Log.i("mouse", "Lost the finger"); |
| 290 if (mMouseButton == BUTTON_UNDEFINED) { |
| 291 // The user pressed and released without moving: do left
click and release. |
| 292 Log.i("mouse", "\tStarting and finishing left click"); |
| 293 handleMouseMovement(coordinates, BUTTON_LEFT, true); |
| 294 mMouseButton = BUTTON_LEFT; |
| 295 mMousePressed = false; |
| 296 } |
| 297 else if (mMousePressed) { |
| 298 Log.i("mouse", "\tReleasing the currently-pressed button
"); |
| 299 mMousePressed = false; |
| 300 } |
| 301 else { |
| 302 Log.w("mouse", "Button already in released state before
gesture ended"); |
| 303 } |
| 304 break; |
| 305 |
| 306 default: |
| 307 return handled; |
| 308 } |
| 309 handleMouseMovement(coordinates, mMouseButton, mMousePressed); |
| 310 |
| 311 return true; |
| 312 } |
| 313 |
| 314 return handled; |
| 234 } | 315 } |
| 235 | 316 |
| 236 /** Responds to touch events filtered by the gesture detectors. */ | 317 /** Responds to touch events filtered by the gesture detectors. */ |
| 237 private class DesktopListener extends GestureDetector.SimpleOnGestureListene
r | 318 private class DesktopListener extends GestureDetector.SimpleOnGestureListene
r |
| 238 implements ScaleGestureDetector.OnScaleGestureListener { | 319 implements ScaleGestureDetector.OnScaleGestureListener { |
| 239 /** | 320 /** |
| 240 * Called when the user is scrolling. We refuse to accept or process the
event unless it | 321 * Called when the user is scrolling. We refuse to accept or process the
event unless it |
| 241 * is being performed with 2 or more touch points, in order to reserve s
ingle-point touch | 322 * is being performed with 2 or more touch points, in order to reserve s
ingle-point touch |
| 242 * events for emulating mouse input. | 323 * events for emulating mouse input. |
| 243 */ | 324 */ |
| (...skipping 21 matching lines...) Expand all Loading... |
| 265 | 346 |
| 266 synchronized (mTransform) { | 347 synchronized (mTransform) { |
| 267 float scaleFactor = detector.getScaleFactor(); | 348 float scaleFactor = detector.getScaleFactor(); |
| 268 mTransform.postScale( | 349 mTransform.postScale( |
| 269 scaleFactor, scaleFactor, detector.getFocusX(), detector
.getFocusY()); | 350 scaleFactor, scaleFactor, detector.getFocusX(), detector
.getFocusY()); |
| 270 } | 351 } |
| 271 JniInterface.redrawGraphics(); | 352 JniInterface.redrawGraphics(); |
| 272 return true; | 353 return true; |
| 273 } | 354 } |
| 274 | 355 |
| 356 /** Called whenever a gesture starts. Always accepts the gesture so it i
sn't ignored. */ |
| 357 @Override |
| 358 public boolean onDown(MotionEvent e) { |
| 359 return true; |
| 360 } |
| 361 |
| 275 /** | 362 /** |
| 276 * Called when the user starts to zoom. Always accepts the zoom so that | 363 * Called when the user starts to zoom. Always accepts the zoom so that |
| 277 * onScale() can decide whether to respond to it. | 364 * onScale() can decide whether to respond to it. |
| 278 */ | 365 */ |
| 279 @Override | 366 @Override |
| 280 public boolean onScaleBegin(ScaleGestureDetector detector) { | 367 public boolean onScaleBegin(ScaleGestureDetector detector) { |
| 281 return true; | 368 return true; |
| 282 } | 369 } |
| 283 | 370 |
| 284 /** | 371 /** Called when the user is done zooming. Defers to onScale()'s judgemen
t. */ |
| 285 * Called when the user is done zooming. Defers to onScale()'s judgement
. | |
| 286 */ | |
| 287 @Override | 372 @Override |
| 288 public void onScaleEnd(ScaleGestureDetector detector) { | 373 public void onScaleEnd(ScaleGestureDetector detector) { |
| 289 onScale(detector); | 374 onScale(detector); |
| 290 } | 375 } |
| 376 |
| 377 /** Called when the user holds down on the screen. Starts a right-click.
*/ |
| 378 @Override |
| 379 public void onLongPress(MotionEvent e) { |
| 380 if (e.getPointerCount() > 1) { |
| 381 return; |
| 382 } |
| 383 |
| 384 float[] coordinates = new float[] {e.getRawX(), e.getY()}; |
| 385 |
| 386 Log.i("mouse", "Finger held down"); |
| 387 if (mMousePressed) { |
| 388 Log.i("mouse", "\tReleasing the currently-pressed button"); |
| 389 handleMouseMovement(coordinates, mMouseButton, false); |
| 390 } |
| 391 |
| 392 Log.i("mouse", "\tStarting right click"); |
| 393 mMouseButton = BUTTON_RIGHT; |
| 394 mMousePressed = true; |
| 395 handleMouseMovement(coordinates, mMouseButton, mMousePressed); |
| 396 } |
| 291 } | 397 } |
| 292 } | 398 } |
| OLD | NEW |