OLD | NEW |
1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 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.graphics.Matrix; | 7 import android.graphics.Matrix; |
8 import android.graphics.PointF; | 8 import android.graphics.PointF; |
9 import android.graphics.Rect; | 9 import android.graphics.Rect; |
10 import android.graphics.RectF; | 10 import android.graphics.RectF; |
| 11 import android.os.SystemClock; |
| 12 import android.view.animation.DecelerateInterpolator; |
| 13 import android.view.animation.Interpolator; |
11 | 14 |
12 /** | 15 /** |
13 * This class is responsible for transforming the desktop image matrix. | 16 * This class is responsible for transforming the desktop image matrix. |
14 */ | 17 */ |
15 public class DesktopCanvas { | 18 public class DesktopCanvas { |
16 /** | 19 /** Used for floating point comparisons. */ |
17 * Maximum allowed zoom level - see {@link #scaleAndRepositionImage()}. | 20 private static final float EPSILON = 0.0001f; |
18 */ | 21 |
| 22 /** Maximum allowed zoom level - see {@link #scaleAndRepositionImage()}. */ |
19 private static final float MAX_ZOOM_FACTOR = 100.0f; | 23 private static final float MAX_ZOOM_FACTOR = 100.0f; |
20 | 24 |
21 /** | |
22 * Used to smoothly reduce the amount of padding while the user is zooming. | |
23 */ | |
24 private static final float PADDING_REDUCTION_FACTOR = 0.85f; | |
25 | |
26 private final RenderStub mRenderStub; | 25 private final RenderStub mRenderStub; |
27 private final RenderData mRenderData; | 26 private final RenderData mRenderData; |
28 | 27 |
29 /** | 28 /** |
30 * Represents the actual center of the viewport in image space. This value
needs to be a pair | |
31 * of floats so the desktop image can be positioned with sub-pixel accuracy
for smoother panning | |
32 * animations at high zoom levels. | |
33 */ | |
34 // TODO(joedow): See if we can collapse Viewport and Cursor position members
. They were needed | |
35 // in the past due to how we calculated the center positions but may not be
needed now. | |
36 private PointF mViewportPosition = new PointF(); | |
37 | |
38 /** | |
39 * Represents the desired center of the viewport in image space. This value
may not represent | 29 * Represents the desired center of the viewport in image space. This value
may not represent |
40 * the actual center of the viewport as adjustments are made to ensure as mu
ch of the desktop is | 30 * the actual center of the viewport as adjustments are made to ensure as mu
ch of the desktop is |
41 * visible as possible. This value needs to be a pair of floats so the desk
top image can be | 31 * visible as possible. This value needs to be a pair of floats so the desk
top image can be |
42 * positioned with sub-pixel accuracy for smoother panning animations at hig
h zoom levels. | 32 * positioned with sub-pixel accuracy for smoother panning animations at hig
h zoom levels. |
43 * Note: The internal cursor position may be placed outside of the image bou
ndary, however the | |
44 * cursor position we inject on the host side is restricted to the actual im
age dimensions. | |
45 */ | 33 */ |
46 private PointF mCursorPosition = new PointF(); | 34 private PointF mDesiredCenter = new PointF(); |
47 | 35 |
48 /** | 36 /** |
49 * Represents the amount of space, in pixels, used by System UI on each edge
of the screen. | 37 * If System UI exists, this contains the area of the screen which is unobsc
ured by it, |
| 38 * otherwise it is empty. |
50 */ | 39 */ |
51 // TODO(joedow): Update usage of this member so it is a true Rect instead of
a set of offsets. | 40 private Rect mSystemUiScreenRect = new Rect(); |
52 private Rect mSystemUiScreenSize = new Rect(); | |
53 | 41 |
54 /** | 42 /** |
55 * Represents the amount of padding, in screen pixels, added to each edge of
the desktop image. | 43 * Represents the amount of space, in pixels, to shift the image under the v
iewport. This value |
56 * This extra space allows the user to reveal portions of the desktop image
which are obscured | 44 * is used to allow panning the image further than would be possible when us
ing the normal |
57 * by System UI. | 45 * boundary values to account for System UI. This functionality ensures the
user can view and |
| 46 * interact with any area of the remote image, even when System UI might oth
erwise obscure it. |
58 */ | 47 */ |
59 private RectF mVisibleImagePadding = new RectF(); | 48 private PointF mViewportOffset = new PointF(); |
60 | 49 |
61 /** | 50 /** |
62 * Tracks whether to adjust the viewport to account for System UI. If false,
the viewport | 51 * Tracks whether to adjust the size of the viewport to account for System U
I. If false, the |
63 * center is the center of the screen. If true, then System UI offsets will
be used to | 52 * viewport center is mapped to the center of the screen. If true, then Sys
tem UI sizes will be |
64 * adjust the position of the viewport to ensure the cursor is visible. | 53 * used to determine the center of the viewport. |
65 */ | 54 */ |
66 private boolean mAdjustViewportForSystemUi = false; | 55 private boolean mAdjustViewportSizeForSystemUi = false; |
67 | 56 |
68 /** | 57 /* Used to perform per-frame rendering tasks. */ |
69 * Represents the amount of space, in pixels, to adjust the cursor center alo
ng the y-axis. | 58 private Event.ParameterCallback<Boolean, Void> mFrameRenderedCallback; |
70 */ | |
71 private float mCursorOffsetScreenY = 0.0f; | |
72 | 59 |
73 public DesktopCanvas(RenderStub renderStub, RenderData renderData) { | 60 public DesktopCanvas(RenderStub renderStub, RenderData renderData) { |
74 mRenderStub = renderStub; | 61 mRenderStub = renderStub; |
75 mRenderData = renderData; | 62 mRenderData = renderData; |
76 } | 63 } |
77 | 64 |
78 /** | 65 /** |
79 * Sets the desired center position of the viewport (a.k.a. the cursor posit
ion) and ensures | 66 * Sets the desired center position of the viewport (a.k.a. the cursor posit
ion). |
80 * the viewport is updated to include the cursor within it. | |
81 * | 67 * |
82 * @param newX The new x coordinate value for the desired center position. | 68 * @param newX The new x coordinate value for the desired center position. |
83 * @param newY The new y coordinate value for the desired center position. | 69 * @param newY The new y coordinate value for the desired center position. |
84 */ | 70 */ |
85 public void setCursorPosition(float newX, float newY) { | 71 public void setCursorPosition(float newX, float newY) { |
86 // First set the cursor position since its potential values are a supers
et of the viewport. | 72 updateCursorPosition(newX, newY, getImageBounds()); |
87 mCursorPosition.set(newX, newY); | 73 } |
88 constrainPointToBounds(mCursorPosition, getImageBounds()); | |
89 | 74 |
90 // Now set the viewport position based on the cursor. | 75 /** |
91 mViewportPosition.set(mCursorPosition); | 76 * Sets the center of the viewport using an absolute position (in image coor
dinates). |
92 constrainPointToBounds(mViewportPosition, getViewportBounds()); | 77 * |
| 78 * @param newX The new x coordinate value for the center position of the vie
wport. |
| 79 * @param newY The new y coordinate value for the center position of the vie
wport. |
| 80 */ |
| 81 public void setViewportCenter(float newX, float newY) { |
| 82 updateCursorPosition(newX, newY, getViewportBounds()); |
| 83 } |
93 | 84 |
94 repositionImage(); | 85 /** |
| 86 * Shifts the cursor by the passed in values (in image coordinates). |
| 87 * |
| 88 * @param deltaX The distance (in image coordinates) to move the cursor alon
g the x-axis. |
| 89 * @param deltaY The distance (in image coordinates) to move the cursor alon
g the y-axis. |
| 90 * @return A point representing the new cursor position. |
| 91 */ |
| 92 public PointF moveCursorPosition(float deltaX, float deltaY) { |
| 93 updateCursorPosition( |
| 94 mDesiredCenter.x + deltaX, mDesiredCenter.y + deltaY, getImageBo
unds()); |
| 95 return new PointF(mDesiredCenter.x, mDesiredCenter.y); |
95 } | 96 } |
96 | 97 |
97 /** | 98 /** |
98 * Shifts the viewport by the passed in values (in image coordinates). | 99 * Shifts the viewport by the passed in values (in image coordinates). |
99 * | 100 * |
100 * @param deltaX The distance (in image coordinates) to move the viewport al
ong the x-axis. | 101 * @param deltaX The distance (in image coordinates) to move the viewport al
ong the x-axis. |
101 * @param deltaY The distance (in image coordinates) to move the viewport al
ong the y-axis. | 102 * @param deltaY The distance (in image coordinates) to move the viewport al
ong the y-axis. |
102 */ | 103 */ |
103 public void moveViewportCenter(float deltaX, float deltaY) { | 104 public void moveViewportCenter(float deltaX, float deltaY) { |
104 // Offset and adjust the viewport center position to fit the screen. | 105 updateCursorPosition( |
105 mViewportPosition.offset(deltaX, deltaY); | 106 mDesiredCenter.x + deltaX, mDesiredCenter.y + deltaY, getViewpor
tBounds()); |
106 constrainPointToBounds(mViewportPosition, getViewportBounds()); | |
107 | |
108 // We don't need to constrain the cursor position as the viewport positi
on is always within | |
109 // the bounds of the potential cursor positions. | |
110 mCursorPosition.set(mViewportPosition); | |
111 | |
112 repositionImage(); | |
113 } | 107 } |
114 | 108 |
115 /** | 109 /** |
116 * Shifts the cursor by the passed in values (in image coordinates) and adju
sts the viewport. | |
117 * | |
118 * @param deltaX The distance (in image coordinates) to move the cursor alon
g the x-axis. | |
119 * @param deltaY The distance (in image coordinates) to move the cursor alon
g the y-axis. | |
120 * @return A point representing the new cursor position. | |
121 */ | |
122 public PointF moveCursorPosition(float deltaX, float deltaY) { | |
123 setCursorPosition(mCursorPosition.x + deltaX, mCursorPosition.y + deltaY
); | |
124 return new PointF(mCursorPosition.x, mCursorPosition.y); | |
125 } | |
126 | |
127 /** | |
128 * Handles System UI size and visibility changes. | 110 * Handles System UI size and visibility changes. |
129 * | 111 * |
130 * @param parameter The set of values defining the current System UI state. | 112 * @param parameter The set of values defining the current System UI state. |
131 */ | 113 */ |
132 public void onSystemUiVisibilityChanged(SystemUiVisibilityChangedEventParame
ter parameter) { | 114 public void onSystemUiVisibilityChanged(SystemUiVisibilityChangedEventParame
ter parameter) { |
133 if (parameter.softInputMethodVisible) { | 115 if (parameter.softInputMethodVisible) { |
134 mSystemUiScreenSize.set(parameter.left, parameter.top, | 116 mSystemUiScreenRect.set( |
135 mRenderData.screenWidth - parameter.right, | 117 parameter.left, parameter.top, parameter.right, parameter.bo
ttom); |
136 mRenderData.screenHeight - parameter.bottom); | |
137 | 118 |
138 if (mAdjustViewportForSystemUi) { | 119 stopOffsetReductionAnimation(); |
139 // Adjust the cursor position to ensure it's visible when large
System UI (1/3 or | 120 } else { |
140 // more of the total screen size) is displayed (typically the So
ft Keyboard). | 121 mSystemUiScreenRect.setEmpty(); |
141 // Without this change, it is difficult for users to enter text
into edit controls | 122 startOffsetReductionAnimation(); |
142 // which are located bottom of the screen and may not see the cu
rsor at all. | 123 } |
143 if (mSystemUiScreenSize.bottom > (mRenderData.screenHeight / 3))
{ | |
144 // Center the cursor within the viewable area (not obscured
by System UI). | |
145 mCursorOffsetScreenY = (float) parameter.bottom / 2.0f; | |
146 } else { | |
147 mCursorOffsetScreenY = 0.0f; | |
148 } | |
149 | 124 |
150 // Apply the cursor offset. | 125 repositionImage(); |
151 setCursorPosition(mCursorPosition.x, mCursorPosition.y); | |
152 } | |
153 } else { | |
154 mCursorOffsetScreenY = 0.0f; | |
155 mSystemUiScreenSize.setEmpty(); | |
156 } | |
157 } | 126 } |
158 | 127 |
159 public void adjustViewportForSystemUi(boolean adjustViewportForSystemUi) { | 128 public void adjustViewportForSystemUi(boolean adjustViewportForSystemUi) { |
160 mAdjustViewportForSystemUi = adjustViewportForSystemUi; | 129 mAdjustViewportSizeForSystemUi = adjustViewportForSystemUi; |
| 130 |
| 131 // The viewport center may have changed so reposition the image to refle
ct the new value. |
| 132 repositionImage(); |
161 } | 133 } |
162 | 134 |
163 /** Resizes the image by zooming it such that the image is displayed without
borders. */ | 135 /** Resizes the image by zooming it such that the image is displayed without
borders. */ |
164 public void resizeImageToFitScreen() { | 136 public void resizeImageToFitScreen() { |
165 // Protect against being called before the image has been initialized. | 137 // Protect against being called before the image has been initialized. |
166 if (mRenderData.imageWidth == 0 || mRenderData.imageHeight == 0) { | 138 if (mRenderData.imageWidth == 0 || mRenderData.imageHeight == 0) { |
167 return; | 139 return; |
168 } | 140 } |
169 | 141 |
170 float widthRatio = (float) mRenderData.screenWidth / mRenderData.imageWi
dth; | 142 float widthRatio = (float) mRenderData.screenWidth / mRenderData.imageWi
dth; |
171 float heightRatio = (float) mRenderData.screenHeight / mRenderData.image
Height; | 143 float heightRatio = (float) mRenderData.screenHeight / mRenderData.image
Height; |
172 float screenToImageScale = Math.max(widthRatio, heightRatio); | 144 float screenToImageScale = Math.max(widthRatio, heightRatio); |
173 | 145 |
174 // If the image is smaller than the screen in either dimension, then we
want to scale it | 146 // If the image is smaller than the screen in either dimension, then we
want to scale it |
175 // up to fit both and fill the screen with the image of the remote deskt
op. | 147 // up to fit both and fill the screen with the image of the remote deskt
op. |
176 if (screenToImageScale > 1.0f) { | 148 if (screenToImageScale > 1.0f) { |
177 mRenderData.transform.setScale(screenToImageScale, screenToImageScal
e); | 149 mRenderData.transform.setScale(screenToImageScale, screenToImageScal
e); |
178 } | 150 } |
179 } | 151 } |
180 | 152 |
181 /** | 153 /** |
182 * Repositions the image by translating and zooming it, to keep the zoom lev
el within sensible | 154 * Repositions the image by translating and zooming it, to keep the zoom lev
el within sensible |
183 * limits. The minimum zoom level is chosen to avoid black space around all
4 sides. The | 155 * limits. The minimum zoom level is chosen to avoid letterboxing on all 4 s
ides. The |
184 * maximum zoom level is set arbitrarily, so that the user can zoom out agai
n in a reasonable | 156 * maximum zoom level is set arbitrarily, so that the user can zoom out agai
n in a reasonable |
185 * time, and to prevent arithmetic overflow problems from displaying the ima
ge. | 157 * time, and to prevent arithmetic overflow problems from displaying the ima
ge. |
186 * | 158 * |
187 * @param scaleFactor The factor used to zoom the canvas in or out. | 159 * @param scaleFactor The factor used to zoom the canvas in or out. |
188 * @param px The center x coordinate for the scale action. | 160 * @param px The center x coordinate for the scale action. |
189 * @param py The center y coordinate for the scale action. | 161 * @param py The center y coordinate for the scale action. |
190 * @param centerOnCursor Determines whether the viewport will be translated
to the desired | 162 * @param centerOnCursor Determines whether the viewport will be translated
to the desired |
191 * center position before being adjusted to fit the sc
reen boundaries. | 163 * center position before being adjusted to fit the sc
reen boundaries. |
192 */ | 164 */ |
193 public void scaleAndRepositionImage( | 165 public void scaleAndRepositionImage( |
(...skipping 16 matching lines...) Expand all Loading... |
210 mRenderData.transform.mapVectors(imageSize); | 182 mRenderData.transform.mapVectors(imageSize); |
211 | 183 |
212 if (imageSize[0] < mRenderData.screenWidth && imageSize[1] < mRenderData
.screenHeight) { | 184 if (imageSize[0] < mRenderData.screenWidth && imageSize[1] < mRenderData
.screenHeight) { |
213 // Displayed image is too small in both directions, so apply the min
imum zoom | 185 // Displayed image is too small in both directions, so apply the min
imum zoom |
214 // level needed to fit either the width or height. | 186 // level needed to fit either the width or height. |
215 float scale = Math.min((float) mRenderData.screenWidth / mRenderData
.imageWidth, | 187 float scale = Math.min((float) mRenderData.screenWidth / mRenderData
.imageWidth, |
216 (float) mRenderData.screenHeight / mRenderDat
a.imageHeight); | 188 (float) mRenderData.screenHeight / mRenderDat
a.imageHeight); |
217 mRenderData.transform.setScale(scale, scale); | 189 mRenderData.transform.setScale(scale, scale); |
218 } | 190 } |
219 | 191 |
220 // Trim the image padding if the user is zooming out. This prevents cas
es where the image | |
221 // pops to the center when it reaches its minimum size. Note that we do
not need to do | |
222 // anything when the user is zooming in as the canvas will expand and ab
sorb the padding. | |
223 if (scaleFactor < 1.0f) { | |
224 mVisibleImagePadding.set(mVisibleImagePadding.left * PADDING_REDUCTI
ON_FACTOR, | |
225 mVisibleImagePadding.top * PADDING_REDUCTION_FACTOR, | |
226 mVisibleImagePadding.right * PADDING_REDUCTION_FACTOR, | |
227 mVisibleImagePadding.bottom * PADDING_REDUCTION_FACTOR); | |
228 } | |
229 | |
230 if (centerOnCursor) { | 192 if (centerOnCursor) { |
231 setCursorPosition(mCursorPosition.x, mCursorPosition.y); | 193 setCursorPosition(mDesiredCenter.x, mDesiredCenter.y); |
232 } else { | 194 } else { |
233 // Find the new screen center (it was probably changed during the zo
om operation) and | 195 // Find the new screen center (it probably changed during the zoom o
peration) and update |
234 // update the viewport and cursor. | 196 // the viewport to smoothly track the zoom gesture. |
235 float[] mappedPoints = { | 197 float[] mappedPoints = {((float) mRenderData.screenWidth / 2) - mVie
wportOffset.x, |
236 ((float) mRenderData.screenWidth / 2), ((float) mRenderData.
screenHeight / 2)}; | 198 ((float) mRenderData.screenHeight / 2) - mViewportOffset.y}; |
237 Matrix screenToImage = new Matrix(); | 199 Matrix screenToImage = new Matrix(); |
238 mRenderData.transform.invert(screenToImage); | 200 mRenderData.transform.invert(screenToImage); |
239 screenToImage.mapPoints(mappedPoints); | 201 screenToImage.mapPoints(mappedPoints); |
240 // The cursor is mapped to the center of the viewport in this case. | 202 // The cursor is mapped to the center of the viewport in this case. |
241 setCursorPosition(mappedPoints[0], mappedPoints[1]); | 203 setViewportCenter(mappedPoints[0], mappedPoints[1]); |
242 } | 204 } |
243 } | 205 } |
244 | 206 |
245 /** | 207 /** |
| 208 * Sets the new cursor position, bounded by the given rect, and updates the
image transform to |
| 209 * reflect the new position. |
| 210 */ |
| 211 private void updateCursorPosition(float newX, float newY, RectF bounds) { |
| 212 mDesiredCenter.set(newX, newY); |
| 213 constrainPointToBounds(mDesiredCenter, bounds); |
| 214 |
| 215 calculateViewportOffset(newX - mDesiredCenter.x, newY - mDesiredCenter.y
); |
| 216 |
| 217 repositionImage(); |
| 218 } |
| 219 |
| 220 /** |
| 221 * Returns the height of the screen (in screen coordinates) for use in calcu
lations involving |
| 222 * viewport positioning. |
| 223 */ |
| 224 private float getAdjustedScreenHeight() { |
| 225 float adjustedScreenHeight; |
| 226 if (mAdjustViewportSizeForSystemUi && !mSystemUiScreenRect.isEmpty()) { |
| 227 // Find the center point of the viewport on the screen. |
| 228 adjustedScreenHeight = mSystemUiScreenRect.bottom; |
| 229 } else { |
| 230 adjustedScreenHeight = ((float) mRenderData.screenHeight); |
| 231 } |
| 232 |
| 233 return adjustedScreenHeight; |
| 234 } |
| 235 |
| 236 /** |
| 237 * Returns the center position of the viewport (in screen coordinates) takin
g System UI into |
| 238 * account. |
| 239 */ |
| 240 private PointF getViewportScreenCenter() { |
| 241 return new PointF((float) mRenderData.screenWidth / 2, getAdjustedScreen
Height() / 2); |
| 242 } |
| 243 |
| 244 /** |
246 * Repositions the image by translating it (without affecting the zoom level
). | 245 * Repositions the image by translating it (without affecting the zoom level
). |
247 */ | 246 */ |
248 private void repositionImage() { | 247 private void repositionImage() { |
249 // Map the current viewport position to screen coordinates and adjust th
e image position. | 248 PointF viewportPosition = new PointF(mDesiredCenter.x, mDesiredCenter.y)
; |
250 float[] viewportPosition = {mViewportPosition.x, mViewportPosition.y}; | 249 constrainPointToBounds(viewportPosition, getViewportBounds()); |
251 mRenderData.transform.mapPoints(viewportPosition); | 250 float[] viewportAdjustment = {viewportPosition.x, viewportPosition.y}; |
| 251 mRenderData.transform.mapPoints(viewportAdjustment); |
252 | 252 |
253 float viewportTransX = ((float) mRenderData.screenWidth / 2) - viewportP
osition[0]; | 253 // Adjust the viewport to include the overpan amount. |
254 float viewportTransY = | 254 viewportAdjustment[0] += mViewportOffset.x; |
255 ((float) mRenderData.screenHeight / 2) - viewportPosition[1] - m
CursorOffsetScreenY; | 255 viewportAdjustment[1] += mViewportOffset.y; |
256 | 256 |
257 // Translate the image to move the viewport to the expected screen locat
ion. | 257 // Translate the image to move the viewport to the expected screen locat
ion. |
258 mRenderData.transform.postTranslate(viewportTransX, viewportTransY); | 258 PointF viewportCenter = getViewportScreenCenter(); |
259 | 259 mRenderData.transform.postTranslate( |
260 updateVisibleImagePadding(); | 260 viewportCenter.x - viewportAdjustment[0], viewportCenter.y - vie
wportAdjustment[1]); |
261 | 261 |
262 mRenderStub.setTransformation(mRenderData.transform); | 262 mRenderStub.setTransformation(mRenderData.transform); |
263 } | 263 } |
264 | 264 |
265 /** | 265 /** |
266 * Updates the given point such that it refers to a coordinate within the bo
unds provided. | 266 * Updates the given point such that it refers to a coordinate within the bo
unds provided. |
267 * | 267 * |
268 * @param point The point to adjust, the original object is modified. | 268 * @param point The point to adjust, the original object is modified. |
269 * @param bounds The bounds used to constrain the point. | 269 * @param bounds The bounds used to constrain the point. |
270 */ | 270 */ |
271 private void constrainPointToBounds(PointF point, RectF bounds) { | 271 private void constrainPointToBounds(PointF point, RectF bounds) { |
272 if (point.x < bounds.left) { | 272 if (point.x < bounds.left) { |
273 point.x = bounds.left; | 273 point.x = bounds.left; |
274 } else if (point.x > bounds.right) { | 274 } else if (point.x > bounds.right) { |
275 point.x = bounds.right; | 275 point.x = bounds.right; |
276 } | 276 } |
277 | 277 |
278 if (point.y < bounds.top) { | 278 if (point.y < bounds.top) { |
279 point.y = bounds.top; | 279 point.y = bounds.top; |
280 } else if (point.y > bounds.bottom) { | 280 } else if (point.y > bounds.bottom) { |
281 point.y = bounds.bottom; | 281 point.y = bounds.bottom; |
282 } | 282 } |
283 } | 283 } |
284 | 284 |
285 /** Returns a region which defines the set of valid cursor positions in imag
e space. */ | 285 /** Returns a region which defines the set of valid cursor positions in imag
e space. */ |
286 private RectF getImageBounds() { | 286 private RectF getImageBounds() { |
287 // The set of valid cursor positions includes any point on the image as
well as the padding. | 287 return new RectF(0, 0, mRenderData.imageWidth, mRenderData.imageHeight); |
288 // Padding is additional space added to the image which is the larger va
lue of: | |
289 // - Potential overlap of the System UI and image content | |
290 // - Actual amount of padding already being used | |
291 // | |
292 // By expanding the area, we allow the user to move the cursor 'under' t
he System UI which | |
293 // pulls the content out from under it and allows it to be visible. Onc
e the System UI has | |
294 // been dismissed or changes size, we use the actual padding value inste
ad which prevents | |
295 // the desktop image from 'snapping' back to pre-System UI state. | |
296 RectF systemUiOverlap = getSystemUiOverlap(); | |
297 float[] padding = {Math.max(mVisibleImagePadding.left, systemUiOverlap.l
eft), | |
298 Math.max(mVisibleImagePadding.top + mCursorOffsetScreenY, system
UiOverlap.top), | |
299 Math.max(mVisibleImagePadding.right, systemUiOverlap.right), | |
300 Math.max(mVisibleImagePadding.bottom - mCursorOffsetScreenY, | |
301 systemUiOverlap.bottom)}; | |
302 Matrix screenToImage = new Matrix(); | |
303 mRenderData.transform.invert(screenToImage); | |
304 screenToImage.mapVectors(padding); | |
305 | |
306 return new RectF(-padding[0], -padding[1], mRenderData.imageWidth + padd
ing[2], | |
307 mRenderData.imageHeight + padding[3]); | |
308 } | 288 } |
309 | 289 |
310 /** Returns a region which defines the set of valid viewport center values i
n image space. */ | 290 /** Returns a region which defines the set of valid viewport center values i
n image space. */ |
311 private RectF getViewportBounds() { | 291 private RectF getViewportBounds() { |
312 // The region of allowable viewport values is the imageBound rect, inset
by the size of the | 292 // The region of allowable viewport values is the imageBound rect, inset
by the size of the |
313 // viewport itself. This prevents over and under panning of the viewpor
t while still | 293 // viewport itself. This prevents over and under panning of the viewpor
t while still |
314 // allowing the user to see and interact with all pixels one the desktop
image. | 294 // allowing the user to see and interact with all pixels one the desktop
image. |
315 Matrix screenToImage = new Matrix(); | 295 Matrix screenToImage = new Matrix(); |
316 mRenderData.transform.invert(screenToImage); | 296 mRenderData.transform.invert(screenToImage); |
317 | 297 |
318 float[] screenVectors = {(float) mRenderData.screenWidth, (float) mRende
rData.screenHeight}; | 298 PointF viewportCenter = getViewportScreenCenter(); |
| 299 float[] screenVectors = {viewportCenter.x, viewportCenter.y}; |
319 screenToImage.mapVectors(screenVectors); | 300 screenToImage.mapVectors(screenVectors); |
320 | 301 |
321 PointF letterboxPadding = getLetterboxPadding(); | 302 PointF letterboxPadding = getLetterboxPadding(); |
322 float[] letterboxPaddingVectors = {letterboxPadding.x, letterboxPadding.
y}; | 303 float[] letterboxPaddingVectors = {letterboxPadding.x, letterboxPadding.
y}; |
323 screenToImage.mapVectors(letterboxPaddingVectors); | 304 screenToImage.mapVectors(letterboxPaddingVectors); |
324 | 305 |
325 // screenCenter values are 1/2 of a particular screen dimension mapped t
o image space. | 306 // screenCenter values are 1/2 of a particular screen dimension mapped t
o image space. |
326 float screenCenterX = (screenVectors[0] / 2.0f) - letterboxPaddingVector
s[0]; | 307 float screenCenterX = screenVectors[0] - letterboxPaddingVectors[0]; |
327 float screenCenterY = (screenVectors[1] / 2.0f) - letterboxPaddingVector
s[1]; | 308 float screenCenterY = screenVectors[1] - letterboxPaddingVectors[1]; |
328 RectF imageBounds = getImageBounds(); | 309 RectF imageBounds = getImageBounds(); |
329 imageBounds.inset(screenCenterX, screenCenterY); | 310 imageBounds.inset(screenCenterX, screenCenterY); |
330 return imageBounds; | 311 return imageBounds; |
331 } | 312 } |
332 | 313 |
333 /** | 314 /** |
| 315 * Returns a region defining the maximum offset distance required to view th
e entire image |
| 316 * given the current amount of System UI overlapping it. |
| 317 */ |
| 318 private RectF getViewportOffsetBounds() { |
| 319 // The allowable region is determined by: |
| 320 // - Overlap of the System UI and image content |
| 321 // - Current viewport offset |
| 322 // |
| 323 // The System UI overlap represents the maximum allowable offset, this i
s used to bound the |
| 324 // viewport movement in each direction. The current offset is used to p
revent 'snapping' |
| 325 // the image when the System UI overlap is reduced. |
| 326 RectF viewportOffsetRect = new RectF(); |
| 327 viewportOffsetRect.union(mViewportOffset.x, mViewportOffset.y); |
| 328 |
| 329 RectF systemUiOverlap = getSystemUiOverlap(); |
| 330 return new RectF(Math.min(viewportOffsetRect.left, -systemUiOverlap.left
), |
| 331 Math.min(viewportOffsetRect.top, -systemUiOverlap.top), |
| 332 Math.max(viewportOffsetRect.right, systemUiOverlap.right), |
| 333 Math.max(viewportOffsetRect.bottom, systemUiOverlap.bottom)); |
| 334 } |
| 335 |
| 336 /** |
334 * Provides the amount of padding needed to center the image content on the
screen. | 337 * Provides the amount of padding needed to center the image content on the
screen. |
335 */ | 338 */ |
336 private PointF getLetterboxPadding() { | 339 private PointF getLetterboxPadding() { |
337 float[] imageVectors = {mRenderData.imageWidth, mRenderData.imageHeight}
; | 340 float[] imageVectors = {mRenderData.imageWidth, mRenderData.imageHeight}
; |
338 mRenderData.transform.mapVectors(imageVectors); | 341 mRenderData.transform.mapVectors(imageVectors); |
339 | 342 |
340 // We want to letterbox when the image is smaller than the screen in a s
pecific dimension. | 343 // We want to letterbox when the image is smaller than the screen in a s
pecific dimension. |
341 // Since we center the image, split the difference so it is equally dist
ributed. | 344 // Since we center the image, split the difference so it is equally dist
ributed. |
342 float widthAdjust = | 345 float widthAdjust = Math.max(((float) mRenderData.screenWidth - imageVec
tors[0]) / 2, 0); |
343 Math.max(((float) mRenderData.screenWidth - imageVectors[0]) / 2
.0f, 0.0f); | 346 float heightAdjust = Math.max((getAdjustedScreenHeight() - imageVectors[
1]) / 2, 0); |
344 float heightAdjust = | |
345 Math.max(((float) mRenderData.screenHeight - imageVectors[1]) /
2.0f, 0.0f); | |
346 | 347 |
347 return new PointF(widthAdjust, heightAdjust); | 348 return new PointF(widthAdjust, heightAdjust); |
348 } | 349 } |
349 | 350 |
350 /** | 351 /** |
351 * Returns the amount of System UI along each edge of the screen which could
overlap the remote | 352 * Returns the amount of System UI along each edge of the screen which could
overlap the remote |
352 * desktop image below it. This is the maximum amount that could overlap, n
ot the actual value. | 353 * desktop image below it. This is the maximum amount that could overlap, n
ot the actual value. |
353 */ | 354 */ |
354 private RectF getSystemUiOverlap() { | 355 private RectF getSystemUiOverlap() { |
355 // letterBox padding represents the space added to the image to center i
t on the screen. | 356 if (mSystemUiScreenRect.isEmpty()) { |
| 357 return new RectF(); |
| 358 } |
| 359 |
| 360 // Letterbox padding represents the space added to the image to center i
t on the screen. |
356 // Since it does not contain any interactable UI, we ignore it when calc
ulating the overlap | 361 // Since it does not contain any interactable UI, we ignore it when calc
ulating the overlap |
357 // between the System UI and the remote desktop image. | 362 // between the System UI and the remote desktop image. |
358 // Note: Ignore negative padding (clamp to 0) since that means no overla
p exists. | 363 // Note: Ignore negative padding (clamp to 0) since that means no overla
p exists. |
| 364 float adjustedScreenHeight = getAdjustedScreenHeight(); |
359 PointF letterboxPadding = getLetterboxPadding(); | 365 PointF letterboxPadding = getLetterboxPadding(); |
360 return new RectF(Math.max(mSystemUiScreenSize.left - letterboxPadding.x,
0.0f), | 366 return new RectF(Math.max(mSystemUiScreenRect.left - letterboxPadding.x,
0.0f), |
361 Math.max(mSystemUiScreenSize.top - letterboxPadding.y + mCursorO
ffsetScreenY, 0.0f), | 367 Math.max(mSystemUiScreenRect.top - letterboxPadding.y, 0.0f), |
362 Math.max(mSystemUiScreenSize.right - letterboxPadding.x, 0.0f), | 368 Math.max(mRenderData.screenWidth - mSystemUiScreenRect.right - l
etterboxPadding.x, |
363 Math.max(mSystemUiScreenSize.bottom - letterboxPadding.y - mCurs
orOffsetScreenY, | 369 0.0f), |
| 370 Math.max(adjustedScreenHeight - mSystemUiScreenRect.bottom - let
terboxPadding.y, |
364 0.0f)); | 371 0.0f)); |
365 } | 372 } |
366 | 373 |
367 /** | 374 /** |
368 * Calculates the amount of padding visible on each edge of the desktop imag
e. | 375 * Applies the given offset, as needed, to allow moving the image outside it
s normal bounds. |
369 */ | 376 */ |
370 private void updateVisibleImagePadding() { | 377 private void calculateViewportOffset(float offsetX, float offsetY) { |
371 PointF letterboxPadding = getLetterboxPadding(); | 378 if (mSystemUiScreenRect.isEmpty()) { |
372 float[] imagePoints = {0.0f, 0.0f, mRenderData.imageWidth, mRenderData.i
mageHeight}; | 379 // We only want to directly change the viewport offset when System U
I is present. |
373 mRenderData.transform.mapPoints(imagePoints); | 380 return; |
| 381 } |
374 | 382 |
375 mVisibleImagePadding.set(Math.max(imagePoints[0] - letterboxPadding.x, 0
.0f), | 383 float[] offsets = {offsetX, offsetY}; |
376 Math.max(imagePoints[1] - letterboxPadding.y, 0.0f), | 384 mRenderData.transform.mapVectors(offsets); |
377 Math.max(mRenderData.screenWidth - imagePoints[2] - letterboxPad
ding.x, 0.0f), | 385 |
378 Math.max(mRenderData.screenHeight - imagePoints[3] - letterboxPa
dding.y, 0.0f)); | 386 // Use a temporary variable here as {@link #getViewportOffsetBounds()} u
ses the current |
| 387 // viewport offset as a maximum boundary. If we add the offset first, t
he result ends up |
| 388 // being unbounded. Thus we use a temporary object for the boundary cal
culation. |
| 389 PointF requestedOffset = |
| 390 new PointF(mViewportOffset.x + offsets[0], mViewportOffset.y + o
ffsets[1]); |
| 391 constrainPointToBounds(requestedOffset, getViewportOffsetBounds()); |
| 392 mViewportOffset.set(requestedOffset); |
| 393 } |
| 394 |
| 395 /** |
| 396 * Starts an animation to smoothly reduce the viewport offset. Does nothing
if an animation is |
| 397 * already running or the offset is already 0. |
| 398 */ |
| 399 private void startOffsetReductionAnimation() { |
| 400 if (mFrameRenderedCallback != null || mViewportOffset.length() < EPSILON
) { |
| 401 return; |
| 402 } |
| 403 |
| 404 mFrameRenderedCallback = new Event.ParameterCallback<Boolean, Void>() { |
| 405 private static final float DURATION_MS = 250.0f; |
| 406 |
| 407 private final Interpolator mInterpolator = new DecelerateInterpolato
r(); |
| 408 |
| 409 private long mStartTime = 0; |
| 410 private float mOriginalX = 0.0f; |
| 411 private float mOriginalY = 0.0f; |
| 412 |
| 413 @Override |
| 414 public Boolean run(Void p) { |
| 415 if (mFrameRenderedCallback == null) { |
| 416 return false; |
| 417 } |
| 418 |
| 419 if (mStartTime == 0) { |
| 420 mStartTime = SystemClock.elapsedRealtime(); |
| 421 mOriginalX = mViewportOffset.x; |
| 422 mOriginalY = mViewportOffset.y; |
| 423 } |
| 424 |
| 425 float progress = (SystemClock.elapsedRealtime() - mStartTime) /
DURATION_MS; |
| 426 if (progress < 1.0f) { |
| 427 float reductionFactor = 1.0f - mInterpolator.getInterpolatio
n(progress); |
| 428 mViewportOffset.set(mOriginalX * reductionFactor, mOriginalY
* reductionFactor); |
| 429 } else { |
| 430 mViewportOffset.set(0.0f, 0.0f); |
| 431 mFrameRenderedCallback = null; |
| 432 } |
| 433 |
| 434 repositionImage(); |
| 435 |
| 436 return mFrameRenderedCallback != null; |
| 437 } |
| 438 }; |
| 439 |
| 440 mRenderStub.onCanvasRendered().addSelfRemovable(mFrameRenderedCallback); |
| 441 } |
| 442 |
| 443 /** |
| 444 * Stops an existing offset reduction animation. |
| 445 */ |
| 446 private void stopOffsetReductionAnimation() { |
| 447 // Setting this value this null will prevent it from continuing to execu
te. |
| 448 mFrameRenderedCallback = null; |
379 } | 449 } |
380 } | 450 } |
OLD | NEW |