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.
5 package org.chromium.chromoting;
7 import android.app.ActionBar;
8 import android.app.Activity;
9 import android.content.Context;
10 import android.graphics.Bitmap;
11 import android.graphics.Canvas;
12 import android.graphics.Color;
13 import android.graphics.Paint;
14 import android.graphics.Point;
15 import android.os.Looper;
16 import android.text.InputType;
17 import android.util.Log;
18 import android.view.inputmethod.InputMethodManager;
19 import android.view.MotionEvent;
20 import android.view.SurfaceHolder;
21 import android.view.SurfaceView;
22 import android.view.inputmethod.EditorInfo;
23 import android.view.inputmethod.InputConnection;
25 import org.chromium.chromoting.jni.JniInterface;
28 * The user interface for viewing and interacting with a specific remote host.
29 * It provides a canvas onto which the video feed is rendered, handles
30 * multitouch pan and zoom gestures, and collects and forwards input events.
32 /** GUI element that holds the drawing canvas. */
33 public class DesktopView extends SurfaceView implements DesktopViewInterface, Runnable,
34 SurfaceHolder.Callback {
35 private RenderData mRenderData;
36 private TouchInputHandler mInputHandler;
37 private ActionBar mActionBar;
39 // Flag to prevent multiple repaint requests from being backed up. Requests for repainting will
40 // be dropped if this is already set to true. This is used by the main thread and the painting
41 // thread, so the access should be synchronized on |mRenderData|.
42 private boolean mRepaintPending;
44 public DesktopView(Activity context) {
47 // Give this view keyboard focus, allowing us to customize the soft keyboard's settings.
48 setFocusableInTouchMode(true);
50 mRenderData = new RenderData();
51 mInputHandler = new TrackingInputHandler(this, context, mRenderData);
52 mActionBar = context.getActionBar();
53 mRepaintPending = false;
55 getHolder().addCallback(this);
59 * Request repainting of the desktop view.
61 void requestRepaint() {
62 synchronized (mRenderData) {
63 if (mRepaintPending) {
66 mRepaintPending = true;
68 JniInterface.redrawGraphics();
71 /** Called whenever the screen configuration is changed. */
72 public void onScreenConfigurationChanged() {
73 mInputHandler.onScreenConfigurationChanged();
77 * Redraws the canvas. This should be done on a non-UI thread or it could
78 * cause the UI to lag. Specifically, it is currently invoked on the native
79 * graphics thread using a JNI.
83 if (Looper.myLooper() == Looper.getMainLooper()) {
84 Log.w("deskview", "Canvas being redrawn on UI thread");
87 Bitmap image = JniInterface.getVideoFrame();
89 int width = image.getWidth();
90 int height = image.getHeight();
91 boolean sizeChanged = false;
92 synchronized (mRenderData) {
93 if (mRenderData.imageWidth != width || mRenderData.imageHeight != height) {
94 // TODO(lambroslambrou): Move this code into a sizeChanged() callback, to be
95 // triggered from JniInterface (on the display thread) when the remote screen size
97 mRenderData.imageWidth = width;
98 mRenderData.imageHeight = height;
103 mInputHandler.onHostSizeChanged(width, height);
106 Canvas canvas = getHolder().lockCanvas();
107 synchronized (mRenderData) {
108 mRepaintPending = false;
109 canvas.setMatrix(mRenderData.transform);
112 canvas.drawColor(Color.BLACK);
113 canvas.drawBitmap(image, 0, 0, new Paint());
114 Bitmap cursorBitmap = JniInterface.getCursorBitmap();
115 if (cursorBitmap != null) {
116 Point hotspot = JniInterface.getCursorHotspot();
117 int bitmapX, bitmapY;
118 synchronized (mRenderData) {
119 bitmapX = mRenderData.cursorPosition.x - hotspot.x;
120 bitmapY = mRenderData.cursorPosition.y - hotspot.y;
122 canvas.drawBitmap(cursorBitmap, bitmapX, bitmapY, new Paint());
124 getHolder().unlockCanvasAndPost(canvas);
128 * Called after the canvas is initially created, then after every subsequent resize, as when
129 * the display is rotated.
132 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
135 synchronized (mRenderData) {
136 mRenderData.screenWidth = width;
137 mRenderData.screenHeight = height;
140 mInputHandler.onClientSizeChanged(width, height);
142 JniInterface.provideRedrawCallback(this);
145 /** Called when the canvas is first created. */
147 public void surfaceCreated(SurfaceHolder holder) {
148 Log.i("deskview", "DesktopView.surfaceCreated(...)");
152 * Called when the canvas is finally destroyed. Marks the canvas as needing a redraw so that it
153 * will not be blank if the user later switches back to our window.
156 public void surfaceDestroyed(SurfaceHolder holder) {
157 Log.i("deskview", "DesktopView.surfaceDestroyed(...)");
159 // Stop this canvas from being redrawn.
160 JniInterface.provideRedrawCallback(null);
163 /** Called when a software keyboard is requested, and specifies its options. */
165 public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
166 // Disables rich input support and instead requests simple key events.
167 outAttrs.inputType = InputType.TYPE_NULL;
169 // Prevents most third-party IMEs from ignoring our Activity's adjustResize preference.
170 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_FULLSCREEN;
172 // Ensures that keyboards will not decide to hide the remote desktop on small displays.
173 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_EXTRACT_UI;
175 // Stops software keyboards from closing as soon as the enter key is pressed.
176 outAttrs.imeOptions |= EditorInfo.IME_MASK_ACTION | EditorInfo.IME_FLAG_NO_ENTER_ACTION;
181 /** Called whenever the user attempts to touch the canvas. */
183 public boolean onTouchEvent(MotionEvent event) {
184 return mInputHandler.onTouchEvent(event);
188 public void injectMouseEvent(int x, int y, int button, boolean pressed) {
189 boolean cursorMoved = false;
190 synchronized (mRenderData) {
191 // Test if the cursor actually moved, which requires repainting the cursor. This
192 // requires that the TouchInputHandler doesn't mutate |mRenderData.cursorPosition|
194 if (x != mRenderData.cursorPosition.x) {
195 mRenderData.cursorPosition.x = x;
198 if (y != mRenderData.cursorPosition.y) {
199 mRenderData.cursorPosition.y = y;
204 if (button == TouchInputHandler.BUTTON_UNDEFINED && !cursorMoved) {
205 // No need to inject anything or repaint.
209 JniInterface.mouseAction(x, y, button, pressed);
211 // TODO(lambroslambrou): Optimize this by only repainting the affected areas.
217 public void showActionBar() {
222 public void showKeyboard() {
223 InputMethodManager inputManager =
224 (InputMethodManager)getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
225 inputManager.showSoftInput(this, 0);
229 public void transformationChanged() {