1ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch// Copyright 2013 The Chromium Authors. All rights reserved.
2ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch// Use of this source code is governed by a BSD-style license that can be
3ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch// found in the LICENSE file.
4ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
5ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdochpackage org.chromium.chromoting;
6ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
70f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)import android.content.Context;
8ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdochimport android.graphics.Bitmap;
9ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdochimport android.graphics.Canvas;
10ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdochimport android.graphics.Color;
11ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdochimport android.graphics.Paint;
12d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)import android.graphics.Point;
13f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)import android.graphics.RadialGradient;
14f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)import android.graphics.Shader;
15ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdochimport android.os.Looper;
16f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)import android.os.SystemClock;
17ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdochimport android.text.InputType;
185d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import android.util.AttributeSet;
19ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdochimport android.util.Log;
20ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdochimport android.view.MotionEvent;
21ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdochimport android.view.SurfaceHolder;
22ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdochimport android.view.SurfaceView;
23ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdochimport android.view.inputmethod.EditorInfo;
24ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdochimport android.view.inputmethod.InputConnection;
25a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)import android.view.inputmethod.InputMethodManager;
26ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
27ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdochimport org.chromium.chromoting.jni.JniInterface;
28ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
29ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch/**
30ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch * The user interface for viewing and interacting with a specific remote host.
31ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch * It provides a canvas onto which the video feed is rendered, handles
32ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch * multitouch pan and zoom gestures, and collects and forwards input events.
33ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch */
34ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch/** GUI element that holds the drawing canvas. */
35a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)public class DesktopView extends SurfaceView implements DesktopViewInterface,
361e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        SurfaceHolder.Callback {
371e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    private RenderData mRenderData;
381e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    private TouchInputHandler mInputHandler;
395d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
405d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    /** The parent Desktop activity. */
415d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    private Desktop mDesktop;
42a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)
431e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    // Flag to prevent multiple repaint requests from being backed up. Requests for repainting will
441e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    // be dropped if this is already set to true. This is used by the main thread and the painting
451e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    // thread, so the access should be synchronized on |mRenderData|.
461e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    private boolean mRepaintPending;
47a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch
48a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    // Flag used to ensure that the SurfaceView is only painted between calls to surfaceCreated()
49a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    // and surfaceDestroyed(). Accessed on main thread and display thread, so this should be
50a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    // synchronized on |mRenderData|.
51a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    private boolean mSurfaceCreated = false;
52a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)
53f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    /** Helper class for displaying the long-press feedback animation. This class is thread-safe. */
54f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    private static class FeedbackAnimator {
55f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        /** Total duration of the animation, in milliseconds. */
56f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        private static final float TOTAL_DURATION_MS = 220;
57f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
58f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        /** Start time of the animation, from {@link SystemClock#uptimeMillis()}. */
59f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        private long mStartTime = 0;
60f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
61f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        private boolean mRunning = false;
62f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
63f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        /** Lock to allow multithreaded access to {@link #mStartTime} and {@link #mRunning}. */
64f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        private Object mLock = new Object();
65f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
66f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        private Paint mPaint = new Paint();
67f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
68f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        public boolean isAnimationRunning() {
69f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            synchronized (mLock) {
70f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                return mRunning;
71f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            }
72f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        }
73f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
74f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        /**
75f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)         * Begins a new animation sequence. After calling this method, the caller should
76f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)         * call {@link #render(Canvas, float, float, float)} periodically whilst
77f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)         * {@link #isAnimationRunning()} returns true.
78f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)         */
79f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        public void startAnimation() {
80f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            synchronized (mLock) {
81f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                mRunning = true;
82f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                mStartTime = SystemClock.uptimeMillis();
83f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            }
84f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        }
85f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
86f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        public void render(Canvas canvas, float x, float y, float size) {
87f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            // |progress| is 0 at the beginning, 1 at the end.
88f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            float progress;
89f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            synchronized (mLock) {
90f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                progress = (SystemClock.uptimeMillis() - mStartTime) / TOTAL_DURATION_MS;
91f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                if (progress >= 1) {
92f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                    mRunning = false;
93f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                    return;
94f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                }
95f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            }
96f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
97f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            // Animation grows from 0 to |size|, and goes from fully opaque to transparent for a
98f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            // seamless fading-out effect. The animation needs to have more than one color so it's
99f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            // visible over any background color.
100f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            float radius = size * progress;
1011320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci            int alpha = (int) ((1 - progress) * 0xff);
102f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
103f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            int transparentBlack = Color.argb(0, 0, 0, 0);
104f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            int white = Color.argb(alpha, 0xff, 0xff, 0xff);
105f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            int black = Color.argb(alpha, 0, 0, 0);
106f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            mPaint.setShader(new RadialGradient(x, y, radius,
107f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                    new int[] {transparentBlack, white, black, transparentBlack},
108f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                    new float[] {0.0f, 0.8f, 0.9f, 1.0f}, Shader.TileMode.CLAMP));
109f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            canvas.drawCircle(x, y, radius, mPaint);
110f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        }
111f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    }
112f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
113f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    private FeedbackAnimator mFeedbackAnimator = new FeedbackAnimator();
114f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
115f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    // Variables to control animation by the TouchInputHandler.
116f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
117f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    /** Protects mInputAnimationRunning. */
118f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    private Object mAnimationLock = new Object();
119f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
120f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    /** Whether the TouchInputHandler has requested animation to be performed. */
121f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    private boolean mInputAnimationRunning = false;
122f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
1235d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    public DesktopView(Context context, AttributeSet attributes) {
1245d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        super(context, attributes);
125ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch
126ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch        // Give this view keyboard focus, allowing us to customize the soft keyboard's settings.
127ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch        setFocusableInTouchMode(true);
128ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch
1291e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        mRenderData = new RenderData();
1301e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        mInputHandler = new TrackingInputHandler(this, context, mRenderData);
1311e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        mRepaintPending = false;
132a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)
133ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch        getHolder().addCallback(this);
1341e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    }
135ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
1365d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    public void setDesktop(Desktop desktop) {
1375d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        mDesktop = desktop;
1385d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    }
1395d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
140f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    /** Request repainting of the desktop view. */
1411e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    void requestRepaint() {
1421e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        synchronized (mRenderData) {
1431e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            if (mRepaintPending) {
1441e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)                return;
1451e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            }
1461e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            mRepaintPending = true;
1471e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        }
1481e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        JniInterface.redrawGraphics();
1491e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    }
150ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
1511e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    /** Called whenever the screen configuration is changed. */
1521e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    public void onScreenConfigurationChanged() {
1531e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        mInputHandler.onScreenConfigurationChanged();
154ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    }
155ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
156ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    /**
157ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch     * Redraws the canvas. This should be done on a non-UI thread or it could
158ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch     * cause the UI to lag. Specifically, it is currently invoked on the native
159ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch     * graphics thread using a JNI.
160ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch     */
161a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    public void paint() {
162f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        long startTimeMs = SystemClock.uptimeMillis();
163f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
164ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch        if (Looper.myLooper() == Looper.getMainLooper()) {
165ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch            Log.w("deskview", "Canvas being redrawn on UI thread");
166ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch        }
167ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
1684e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)        Bitmap image = JniInterface.getVideoFrame();
169f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        if (image == null) {
170f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            // This can happen if the client is connected, but a complete video frame has not yet
171f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            // been decoded.
172f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            return;
173f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        }
174ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
1751e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        int width = image.getWidth();
1761e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        int height = image.getHeight();
1771e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        boolean sizeChanged = false;
1781e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        synchronized (mRenderData) {
1791e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            if (mRenderData.imageWidth != width || mRenderData.imageHeight != height) {
1801e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)                // TODO(lambroslambrou): Move this code into a sizeChanged() callback, to be
1811e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)                // triggered from JniInterface (on the display thread) when the remote screen size
1821e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)                // changes.
1831e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)                mRenderData.imageWidth = width;
1841e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)                mRenderData.imageHeight = height;
1851e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)                sizeChanged = true;
186ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch            }
1871e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        }
1881e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        if (sizeChanged) {
1891e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            mInputHandler.onHostSizeChanged(width, height);
1901e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        }
191ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
192a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)        Canvas canvas;
193f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        int x, y;
1941e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        synchronized (mRenderData) {
1951e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            mRepaintPending = false;
196a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)            // Don't try to lock the canvas before it is ready, as the implementation of
197a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)            // lockCanvas() may throttle these calls to a slow rate in order to avoid consuming CPU.
198a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)            // Note that a successful call to lockCanvas() will prevent the framework from
199a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)            // destroying the Surface until it is unlocked.
200a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)            if (!mSurfaceCreated) {
201a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)                return;
202a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)            }
203a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)            canvas = getHolder().lockCanvas();
204a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)            if (canvas == null) {
205a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)                return;
206a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)            }
2071e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            canvas.setMatrix(mRenderData.transform);
208f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            x = mRenderData.cursorPosition.x;
209f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            y = mRenderData.cursorPosition.y;
210ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch        }
211ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
212ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch        canvas.drawColor(Color.BLACK);
213ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch        canvas.drawBitmap(image, 0, 0, new Paint());
214f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
215f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        boolean feedbackAnimationRunning = mFeedbackAnimator.isAnimationRunning();
216f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        if (feedbackAnimationRunning) {
217f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            float scaleFactor;
218f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            synchronized (mRenderData) {
219f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                scaleFactor = mRenderData.transform.mapRadius(1);
220f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            }
221f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            mFeedbackAnimator.render(canvas, x, y, 40 / scaleFactor);
222f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        }
223f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
224d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)        Bitmap cursorBitmap = JniInterface.getCursorBitmap();
225d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)        if (cursorBitmap != null) {
226d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)            Point hotspot = JniInterface.getCursorHotspot();
227f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            canvas.drawBitmap(cursorBitmap, x - hotspot.x, y - hotspot.y, new Paint());
228d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)        }
229f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
230ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch        getHolder().unlockCanvasAndPost(canvas);
231f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
232f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        synchronized (mAnimationLock) {
233f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            if (mInputAnimationRunning || feedbackAnimationRunning) {
234f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                getHandler().postAtTime(new Runnable() {
235f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                    @Override
236f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                    public void run() {
237f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                        processAnimation();
238f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                    }
239f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                }, startTimeMs + 30);
240f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            }
241f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        };
242f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    }
243f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
244f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    private void processAnimation() {
245f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        boolean running;
246f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        synchronized (mAnimationLock) {
247f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            running = mInputAnimationRunning;
248f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        }
249f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        if (running) {
250f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            mInputHandler.processAnimation();
251f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        }
252f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        running |= mFeedbackAnimator.isAnimationRunning();
253f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        if (running) {
254f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            requestRepaint();
255f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        }
256ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    }
257ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
258a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)    /**
2591e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)     * Called after the canvas is initially created, then after every subsequent resize, as when
2601e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)     * the display is rotated.
261ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch     */
262ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    @Override
2631e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
2641e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        synchronized (mRenderData) {
2651e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            mRenderData.screenWidth = width;
2661e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            mRenderData.screenHeight = height;
267ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch        }
268ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
269a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)        JniInterface.provideRedrawCallback(new Runnable() {
270a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)            @Override
271a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)            public void run() {
272a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)                paint();
273a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)            }
274a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)        });
2751e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        mInputHandler.onClientSizeChanged(width, height);
276a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)        requestRepaint();
277ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    }
278ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
279ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    /** Called when the canvas is first created. */
280ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    @Override
281ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    public void surfaceCreated(SurfaceHolder holder) {
282a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)        synchronized (mRenderData) {
283a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)            mSurfaceCreated = true;
284a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)        }
285ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    }
286ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
287558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch    /**
288558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch     * Called when the canvas is finally destroyed. Marks the canvas as needing a redraw so that it
289558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch     * will not be blank if the user later switches back to our window.
290558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch     */
291ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    @Override
292ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    public void surfaceDestroyed(SurfaceHolder holder) {
293a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)        // Stop this canvas from being redrawn.
294a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)        JniInterface.provideRedrawCallback(null);
295a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)
296a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)        synchronized (mRenderData) {
297a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)            mSurfaceCreated = false;
298a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)        }
299ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    }
300ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
301ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch    /** Called when a software keyboard is requested, and specifies its options. */
302ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch    @Override
303ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
304ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch        // Disables rich input support and instead requests simple key events.
305ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch        outAttrs.inputType = InputType.TYPE_NULL;
306ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch
307ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch        // Prevents most third-party IMEs from ignoring our Activity's adjustResize preference.
308ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch        outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_FULLSCREEN;
309ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch
310ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch        // Ensures that keyboards will not decide to hide the remote desktop on small displays.
311ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch        outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_EXTRACT_UI;
312ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch
313ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch        // Stops software keyboards from closing as soon as the enter key is pressed.
314ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch        outAttrs.imeOptions |= EditorInfo.IME_MASK_ACTION | EditorInfo.IME_FLAG_NO_ENTER_ACTION;
315ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch
316ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch        return null;
317ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch    }
318ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch
3191e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    /** Called whenever the user attempts to touch the canvas. */
320ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    @Override
321ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    public boolean onTouchEvent(MotionEvent event) {
3221e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        return mInputHandler.onTouchEvent(event);
323ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    }
324ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
3251e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    @Override
3261e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    public void injectMouseEvent(int x, int y, int button, boolean pressed) {
3271e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        boolean cursorMoved = false;
3281e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        synchronized (mRenderData) {
3291e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            // Test if the cursor actually moved, which requires repainting the cursor. This
3301e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            // requires that the TouchInputHandler doesn't mutate |mRenderData.cursorPosition|
3311e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            // directly.
3321e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            if (x != mRenderData.cursorPosition.x) {
3331e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)                mRenderData.cursorPosition.x = x;
3341e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)                cursorMoved = true;
335ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch            }
3361e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            if (y != mRenderData.cursorPosition.y) {
3371e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)                mRenderData.cursorPosition.y = y;
3381e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)                cursorMoved = true;
339ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch            }
340ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch        }
341ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
3421e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        if (button == TouchInputHandler.BUTTON_UNDEFINED && !cursorMoved) {
3431e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            // No need to inject anything or repaint.
3441e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            return;
345a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch        }
346a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch
347effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch        JniInterface.sendMouseEvent(x, y, button, pressed);
3481e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        if (cursorMoved) {
3491e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            // TODO(lambroslambrou): Optimize this by only repainting the affected areas.
3501e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)            requestRepaint();
351ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch        }
3521e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    }
353ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
3541e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    @Override
355f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    public void injectMouseWheelDeltaEvent(int deltaX, int deltaY) {
356effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch        JniInterface.sendMouseWheelEvent(deltaX, deltaY);
357f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    }
358f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
359f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    @Override
360f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    public void showLongPressFeedback() {
361f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        mFeedbackAnimator.startAnimation();
362f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        requestRepaint();
363f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    }
364f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
365f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    @Override
3661e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    public void showActionBar() {
3675d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        mDesktop.showActionBar();
3681e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    }
369a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch
3701e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    @Override
3711e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    public void showKeyboard() {
3720f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)        InputMethodManager inputManager =
3731320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
3740f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)        inputManager.showSoftInput(this, 0);
3751e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    }
376a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch
3771e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    @Override
3781e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    public void transformationChanged() {
3791e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        requestRepaint();
380ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    }
381f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
382f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    @Override
383f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    public void setAnimationEnabled(boolean enabled) {
384f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        synchronized (mAnimationLock) {
385f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            if (enabled && !mInputAnimationRunning) {
386f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                requestRepaint();
387f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            }
388f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            mInputAnimationRunning = enabled;
389f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        }
390f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    }
391ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch}
392