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 |