- add sources.
[platform/framework/web/crosswalk.git] / src / remoting / android / java / src / org / chromium / chromoting / DesktopView.java
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.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;
24
25 import org.chromium.chromoting.jni.JniInterface;
26
27 /**
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.
31  */
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;
38
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;
43
44     public DesktopView(Activity context) {
45         super(context);
46
47         // Give this view keyboard focus, allowing us to customize the soft keyboard's settings.
48         setFocusableInTouchMode(true);
49
50         mRenderData = new RenderData();
51         mInputHandler = new TrackingInputHandler(this, context, mRenderData);
52         mActionBar = context.getActionBar();
53         mRepaintPending = false;
54
55         getHolder().addCallback(this);
56     }
57
58     /**
59      * Request repainting of the desktop view.
60      */
61     void requestRepaint() {
62         synchronized (mRenderData) {
63             if (mRepaintPending) {
64                 return;
65             }
66             mRepaintPending = true;
67         }
68         JniInterface.redrawGraphics();
69     }
70
71     /** Called whenever the screen configuration is changed. */
72     public void onScreenConfigurationChanged() {
73         mInputHandler.onScreenConfigurationChanged();
74     }
75
76     /**
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.
80      */
81     @Override
82     public void run() {
83         if (Looper.myLooper() == Looper.getMainLooper()) {
84             Log.w("deskview", "Canvas being redrawn on UI thread");
85         }
86
87         Bitmap image = JniInterface.getVideoFrame();
88
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
96                 // changes.
97                 mRenderData.imageWidth = width;
98                 mRenderData.imageHeight = height;
99                 sizeChanged = true;
100             }
101         }
102         if (sizeChanged) {
103             mInputHandler.onHostSizeChanged(width, height);
104         }
105
106         Canvas canvas = getHolder().lockCanvas();
107         synchronized (mRenderData) {
108             mRepaintPending = false;
109             canvas.setMatrix(mRenderData.transform);
110         }
111
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;
121             }
122             canvas.drawBitmap(cursorBitmap, bitmapX, bitmapY, new Paint());
123         }
124         getHolder().unlockCanvasAndPost(canvas);
125     }
126
127     /**
128      * Called after the canvas is initially created, then after every subsequent resize, as when
129      * the display is rotated.
130      */
131     @Override
132     public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
133         mActionBar.hide();
134
135         synchronized (mRenderData) {
136             mRenderData.screenWidth = width;
137             mRenderData.screenHeight = height;
138         }
139
140         mInputHandler.onClientSizeChanged(width, height);
141
142         JniInterface.provideRedrawCallback(this);
143     }
144
145     /** Called when the canvas is first created. */
146     @Override
147     public void surfaceCreated(SurfaceHolder holder) {
148         Log.i("deskview", "DesktopView.surfaceCreated(...)");
149     }
150
151     /**
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.
154      */
155     @Override
156     public void surfaceDestroyed(SurfaceHolder holder) {
157         Log.i("deskview", "DesktopView.surfaceDestroyed(...)");
158
159         // Stop this canvas from being redrawn.
160         JniInterface.provideRedrawCallback(null);
161     }
162
163     /** Called when a software keyboard is requested, and specifies its options. */
164     @Override
165     public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
166         // Disables rich input support and instead requests simple key events.
167         outAttrs.inputType = InputType.TYPE_NULL;
168
169         // Prevents most third-party IMEs from ignoring our Activity's adjustResize preference.
170         outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_FULLSCREEN;
171
172         // Ensures that keyboards will not decide to hide the remote desktop on small displays.
173         outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_EXTRACT_UI;
174
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;
177
178         return null;
179     }
180
181     /** Called whenever the user attempts to touch the canvas. */
182     @Override
183     public boolean onTouchEvent(MotionEvent event) {
184         return mInputHandler.onTouchEvent(event);
185     }
186
187     @Override
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|
193             // directly.
194             if (x != mRenderData.cursorPosition.x) {
195                 mRenderData.cursorPosition.x = x;
196                 cursorMoved = true;
197             }
198             if (y != mRenderData.cursorPosition.y) {
199                 mRenderData.cursorPosition.y = y;
200                 cursorMoved = true;
201             }
202         }
203
204         if (button == TouchInputHandler.BUTTON_UNDEFINED && !cursorMoved) {
205             // No need to inject anything or repaint.
206             return;
207         }
208
209         JniInterface.mouseAction(x, y, button, pressed);
210         if (cursorMoved) {
211             // TODO(lambroslambrou): Optimize this by only repainting the affected areas.
212             requestRepaint();
213         }
214     }
215
216     @Override
217     public void showActionBar() {
218         mActionBar.show();
219     }
220
221     @Override
222     public void showKeyboard() {
223         InputMethodManager inputManager =
224                 (InputMethodManager)getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
225         inputManager.showSoftInput(this, 0);
226     }
227
228     @Override
229     public void transformationChanged() {
230         requestRepaint();
231     }
232 }