| OLD | NEW |
| (Empty) |
| 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 | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 package org.chromium.chromoting; | |
| 6 | |
| 7 import android.graphics.Bitmap; | |
| 8 import android.graphics.Canvas; | |
| 9 import android.graphics.Color; | |
| 10 import android.graphics.Paint; | |
| 11 import android.graphics.Point; | |
| 12 import android.os.Looper; | |
| 13 import android.os.SystemClock; | |
| 14 import android.view.SurfaceHolder; | |
| 15 | |
| 16 import org.chromium.base.Log; | |
| 17 import org.chromium.chromoting.jni.Client; | |
| 18 import org.chromium.chromoting.jni.Display; | |
| 19 | |
| 20 /** | |
| 21 * The user interface for viewing and interacting with a specific remote host. | |
| 22 * It provides a canvas onto which the video feed is rendered, handles | |
| 23 * multitouch pan and zoom gestures, and collects and forwards input events. | |
| 24 */ | |
| 25 /** GUI element that holds the drawing canvas. */ | |
| 26 public class DesktopView extends AbstractDesktopView implements SurfaceHolder.Ca
llback { | |
| 27 private static final String TAG = "Chromoting"; | |
| 28 | |
| 29 private final Display mDisplay; | |
| 30 | |
| 31 | |
| 32 // Flag to prevent multiple repaint requests from being backed up. Requests
for repainting will | |
| 33 // be dropped if this is already set to true. This is used by the main threa
d and the painting | |
| 34 // thread, so the access should be synchronized on |mRenderData|. | |
| 35 private boolean mRepaintPending; | |
| 36 | |
| 37 // Flag used to ensure that the SurfaceView is only painted between calls to
surfaceCreated() | |
| 38 // and surfaceDestroyed(). Accessed on main thread and display thread, so th
is should be | |
| 39 // synchronized on |mRenderData|. | |
| 40 private boolean mSurfaceCreated = false; | |
| 41 | |
| 42 private final Event.Raisable<PaintEventParameter> mOnPaint = new Event.Raisa
ble<>(); | |
| 43 | |
| 44 // Variables to control animation by the TouchInputHandler. | |
| 45 | |
| 46 /** Protects mInputAnimationRunning. */ | |
| 47 private final Object mAnimationLock = new Object(); | |
| 48 | |
| 49 /** Whether the TouchInputHandler has requested animation to be performed. *
/ | |
| 50 private boolean mInputAnimationRunning = false; | |
| 51 | |
| 52 public DesktopView(Display display, Desktop desktop, Client client) { | |
| 53 super(desktop, client); | |
| 54 Preconditions.notNull(display); | |
| 55 mDisplay = display; | |
| 56 | |
| 57 mRepaintPending = false; | |
| 58 | |
| 59 getHolder().addCallback(this); | |
| 60 | |
| 61 attachRedrawCallback(); | |
| 62 } | |
| 63 | |
| 64 public Event<PaintEventParameter> onPaint() { | |
| 65 return mOnPaint; | |
| 66 } | |
| 67 | |
| 68 /** Request repainting of the desktop view. */ | |
| 69 void requestRepaint() { | |
| 70 synchronized (mRenderData) { | |
| 71 if (mRepaintPending) { | |
| 72 return; | |
| 73 } | |
| 74 mRepaintPending = true; | |
| 75 } | |
| 76 mDisplay.redrawGraphics(); | |
| 77 } | |
| 78 | |
| 79 /** | |
| 80 * Redraws the canvas. This should be done on a non-UI thread or it could | |
| 81 * cause the UI to lag. Specifically, it is currently invoked on the native | |
| 82 * graphics thread using a JNI. | |
| 83 */ | |
| 84 public void paint() { | |
| 85 long startTimeMs = SystemClock.uptimeMillis(); | |
| 86 | |
| 87 if (Looper.myLooper() == Looper.getMainLooper()) { | |
| 88 Log.w(TAG, "Canvas being redrawn on UI thread"); | |
| 89 } | |
| 90 | |
| 91 Bitmap image = mDisplay.getVideoFrame(); | |
| 92 synchronized (mRenderData) { | |
| 93 mRepaintPending = false; | |
| 94 } | |
| 95 if (image == null) { | |
| 96 // This can happen if the client is connected, but a complete video
frame has not yet | |
| 97 // been decoded. | |
| 98 return; | |
| 99 } | |
| 100 | |
| 101 int width = image.getWidth(); | |
| 102 int height = image.getHeight(); | |
| 103 boolean sizeChanged = false; | |
| 104 synchronized (mRenderData) { | |
| 105 if (mRenderData.imageWidth != width || mRenderData.imageHeight != he
ight) { | |
| 106 // TODO(lambroslambrou): Move this code into a sizeChanged() cal
lback, to be | |
| 107 // triggered from native code (on the display thread) when the r
emote screen size | |
| 108 // changes. | |
| 109 mRenderData.imageWidth = width; | |
| 110 mRenderData.imageHeight = height; | |
| 111 sizeChanged = true; | |
| 112 } | |
| 113 } | |
| 114 if (sizeChanged) { | |
| 115 mOnHostSizeChanged.raise(new SizeChangedEventParameter(width, height
)); | |
| 116 } | |
| 117 | |
| 118 Canvas canvas; | |
| 119 Point cursorPosition; | |
| 120 boolean drawCursor; | |
| 121 synchronized (mRenderData) { | |
| 122 // Don't try to lock the canvas before it is ready, as the implement
ation of | |
| 123 // lockCanvas() may throttle these calls to a slow rate in order to
avoid consuming CPU. | |
| 124 // Note that a successful call to lockCanvas() will prevent the fram
ework from | |
| 125 // destroying the Surface until it is unlocked. | |
| 126 if (!mSurfaceCreated) { | |
| 127 return; | |
| 128 } | |
| 129 canvas = getHolder().lockCanvas(); | |
| 130 if (canvas == null) { | |
| 131 return; | |
| 132 } | |
| 133 canvas.setMatrix(mRenderData.transform); | |
| 134 drawCursor = mRenderData.drawCursor; | |
| 135 cursorPosition = mRenderData.getCursorPosition(); | |
| 136 } | |
| 137 | |
| 138 canvas.drawColor(Color.BLACK); | |
| 139 canvas.drawBitmap(image, 0, 0, new Paint()); | |
| 140 | |
| 141 float scaleFactor; | |
| 142 synchronized (mRenderData) { | |
| 143 scaleFactor = mRenderData.transform.mapRadius(1); | |
| 144 } | |
| 145 mOnPaint.raise(new PaintEventParameter(cursorPosition, canvas, scaleFact
or)); | |
| 146 | |
| 147 if (drawCursor) { | |
| 148 Bitmap cursorBitmap = mDisplay.getCursorBitmap(); | |
| 149 if (cursorBitmap != null) { | |
| 150 Point hotspot = mDisplay.getCursorHotspot(); | |
| 151 canvas.drawBitmap(cursorBitmap, cursorPosition.x - hotspot.x, | |
| 152 cursorPosition.y - hotspot.y, new Paint()); | |
| 153 } | |
| 154 } | |
| 155 | |
| 156 getHolder().unlockCanvasAndPost(canvas); | |
| 157 | |
| 158 synchronized (mAnimationLock) { | |
| 159 if (mInputAnimationRunning || !mOnPaint.isEmpty()) { | |
| 160 getHandler().postAtTime(new Runnable() { | |
| 161 @Override | |
| 162 public void run() { | |
| 163 processAnimation(); | |
| 164 } | |
| 165 }, startTimeMs + 30); | |
| 166 } | |
| 167 } | |
| 168 } | |
| 169 | |
| 170 private void processAnimation() { | |
| 171 boolean running; | |
| 172 synchronized (mAnimationLock) { | |
| 173 running = mInputAnimationRunning; | |
| 174 } | |
| 175 if (running) { | |
| 176 mInputHandler.processAnimation(); | |
| 177 requestRepaint(); | |
| 178 } else if (!mOnPaint.isEmpty()) { | |
| 179 requestRepaint(); | |
| 180 } | |
| 181 } | |
| 182 | |
| 183 /** | |
| 184 * Called after the canvas is initially created, then after every subsequent
resize, as when | |
| 185 * the display is rotated. | |
| 186 */ | |
| 187 @Override | |
| 188 public void surfaceChanged(SurfaceHolder holder, int format, int width, int
height) { | |
| 189 synchronized (mRenderData) { | |
| 190 mRenderData.screenWidth = width; | |
| 191 mRenderData.screenHeight = height; | |
| 192 } | |
| 193 | |
| 194 mOnClientSizeChanged.raise(new SizeChangedEventParameter(width, height))
; | |
| 195 requestRepaint(); | |
| 196 } | |
| 197 | |
| 198 public void attachRedrawCallback() { | |
| 199 mDisplay.provideRedrawCallback(new Runnable() { | |
| 200 @Override | |
| 201 public void run() { | |
| 202 paint(); | |
| 203 } | |
| 204 }); | |
| 205 } | |
| 206 | |
| 207 /** Called when the canvas is first created. */ | |
| 208 @Override | |
| 209 public void surfaceCreated(SurfaceHolder holder) { | |
| 210 synchronized (mRenderData) { | |
| 211 mSurfaceCreated = true; | |
| 212 } | |
| 213 } | |
| 214 | |
| 215 /** | |
| 216 * Called when the canvas is finally destroyed. Marks the canvas as needing
a redraw so that it | |
| 217 * will not be blank if the user later switches back to our window. | |
| 218 */ | |
| 219 @Override | |
| 220 public void surfaceDestroyed(SurfaceHolder holder) { | |
| 221 synchronized (mRenderData) { | |
| 222 mSurfaceCreated = false; | |
| 223 } | |
| 224 } | |
| 225 | |
| 226 @Override | |
| 227 public void showInputFeedback(InputFeedbackType feedbackToShow, Point pos) { | |
| 228 float radius = getFeedbackRadius(feedbackToShow); | |
| 229 if (radius <= 0.0f) { | |
| 230 return; | |
| 231 } | |
| 232 FeedbackAnimator.startAnimation(this, pos, radius); | |
| 233 requestRepaint(); | |
| 234 } | |
| 235 | |
| 236 @Override | |
| 237 public void transformationChanged() { | |
| 238 requestRepaint(); | |
| 239 } | |
| 240 | |
| 241 @Override | |
| 242 public void cursorMoved() { | |
| 243 // For current implementation, cursorMoved() is always followed by trans
formationChanged() | |
| 244 // even if the canvas isn't really changed. For future we should improve
this by not calling | |
| 245 // transformationChanged() if the cursor is moved but the canvas is not
changed. | |
| 246 } | |
| 247 | |
| 248 @Override | |
| 249 public void cursorVisibilityChanged() { | |
| 250 requestRepaint(); | |
| 251 } | |
| 252 | |
| 253 @Override | |
| 254 public void setAnimationEnabled(boolean enabled) { | |
| 255 synchronized (mAnimationLock) { | |
| 256 if (enabled && !mInputAnimationRunning) { | |
| 257 requestRepaint(); | |
| 258 } | |
| 259 mInputAnimationRunning = enabled; | |
| 260 } | |
| 261 } | |
| 262 } | |
| OLD | NEW |