Initial import of VNCFlinger
diff --git a/src/EglWindow.cpp b/src/EglWindow.cpp
new file mode 100644
index 0000000..c534b02
--- /dev/null
+++ b/src/EglWindow.cpp
@@ -0,0 +1,189 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "VNC-EglWindow"
+//#define LOG_NDEBUG 0
+#include <utils/Log.h>
+
+#define EGL_EGLEXT_PROTOTYPES
+
+#include <gui/BufferQueue.h>
+#include <gui/GraphicBufferAlloc.h>
+#include <gui/Surface.h>
+
+#include "EglWindow.h"
+
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+
+#include <assert.h>
+
+using namespace android;
+
+
+status_t EglWindow::createWindow(const sp<IGraphicBufferProducer>& surface) {
+    if (mEglSurface != EGL_NO_SURFACE) {
+        ALOGE("surface already created");
+        return UNKNOWN_ERROR;
+    }
+    status_t err = eglSetupContext(false);
+    if (err != NO_ERROR) {
+        return err;
+    }
+
+    // Cache the current dimensions.  We're not expecting these to change.
+    surface->query(NATIVE_WINDOW_WIDTH, &mWidth);
+    surface->query(NATIVE_WINDOW_HEIGHT, &mHeight);
+
+    // Output side (EGL surface to draw on).
+    sp<ANativeWindow> anw = new Surface(surface);
+    mEglSurface = eglCreateWindowSurface(mEglDisplay, mEglConfig, anw.get(),
+            NULL);
+    if (mEglSurface == EGL_NO_SURFACE) {
+        ALOGE("eglCreateWindowSurface error: %#x", eglGetError());
+        eglRelease();
+        return UNKNOWN_ERROR;
+    }
+
+    return NO_ERROR;
+}
+
+status_t EglWindow::createPbuffer(int width, int height) {
+    if (mEglSurface != EGL_NO_SURFACE) {
+        ALOGE("surface already created");
+        return UNKNOWN_ERROR;
+    }
+    status_t err = eglSetupContext(true);
+    if (err != NO_ERROR) {
+        return err;
+    }
+
+    mWidth = width;
+    mHeight = height;
+
+    EGLint pbufferAttribs[] = {
+            EGL_WIDTH, width,
+            EGL_HEIGHT, height,
+            EGL_NONE
+    };
+    mEglSurface = eglCreatePbufferSurface(mEglDisplay, mEglConfig, pbufferAttribs);
+    if (mEglSurface == EGL_NO_SURFACE) {
+        ALOGE("eglCreatePbufferSurface error: %#x", eglGetError());
+        eglRelease();
+        return UNKNOWN_ERROR;
+    }
+
+    return NO_ERROR;
+}
+
+status_t EglWindow::makeCurrent() const {
+    if (!eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
+        ALOGE("eglMakeCurrent failed: %#x", eglGetError());
+        return UNKNOWN_ERROR;
+    }
+    return NO_ERROR;
+}
+
+status_t EglWindow::eglSetupContext(bool forPbuffer) {
+    EGLBoolean result;
+
+    mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+    if (mEglDisplay == EGL_NO_DISPLAY) {
+        ALOGE("eglGetDisplay failed: %#x", eglGetError());
+        return UNKNOWN_ERROR;
+    }
+
+    EGLint majorVersion, minorVersion;
+    result = eglInitialize(mEglDisplay, &majorVersion, &minorVersion);
+    if (result != EGL_TRUE) {
+        ALOGE("eglInitialize failed: %#x", eglGetError());
+        return UNKNOWN_ERROR;
+    }
+    ALOGV("Initialized EGL v%d.%d", majorVersion, minorVersion);
+
+    EGLint numConfigs = 0;
+    EGLint windowConfigAttribs[] = {
+            EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
+            EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+            EGL_RECORDABLE_ANDROID, 1,
+            EGL_RED_SIZE, 8,
+            EGL_GREEN_SIZE, 8,
+            EGL_BLUE_SIZE, 8,
+            EGL_ALPHA_SIZE, 8,
+    };
+    EGLint pbufferConfigAttribs[] = {
+            EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
+            EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+            EGL_RED_SIZE, 8,
+            EGL_GREEN_SIZE, 8,
+            EGL_BLUE_SIZE, 8,
+            EGL_ALPHA_SIZE, 8,
+            EGL_NONE
+    };
+    result = eglChooseConfig(mEglDisplay,
+            forPbuffer ? pbufferConfigAttribs : windowConfigAttribs,
+            &mEglConfig, 1, &numConfigs);
+    if (result != EGL_TRUE) {
+        ALOGE("eglChooseConfig error: %#x", eglGetError());
+        return UNKNOWN_ERROR;
+    }
+
+    EGLint contextAttribs[] = {
+        EGL_CONTEXT_CLIENT_VERSION, 2,
+        EGL_NONE
+    };
+    mEglContext = eglCreateContext(mEglDisplay, mEglConfig, EGL_NO_CONTEXT,
+            contextAttribs);
+    if (mEglContext == EGL_NO_CONTEXT) {
+        ALOGE("eglCreateContext error: %#x", eglGetError());
+        return UNKNOWN_ERROR;
+    }
+
+    return NO_ERROR;
+}
+
+void EglWindow::eglRelease() {
+    ALOGV("EglWindow::eglRelease");
+    if (mEglDisplay != EGL_NO_DISPLAY) {
+        eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE,
+                EGL_NO_CONTEXT);
+
+        if (mEglContext != EGL_NO_CONTEXT) {
+            eglDestroyContext(mEglDisplay, mEglContext);
+        }
+
+        if (mEglSurface != EGL_NO_SURFACE) {
+            eglDestroySurface(mEglDisplay, mEglSurface);
+        }
+    }
+
+    mEglDisplay = EGL_NO_DISPLAY;
+    mEglContext = EGL_NO_CONTEXT;
+    mEglSurface = EGL_NO_SURFACE;
+    mEglConfig = NULL;
+
+    eglReleaseThread();
+}
+
+// Sets the presentation time on the current EGL buffer.
+void EglWindow::presentationTime(nsecs_t whenNsec) const {
+    eglPresentationTimeANDROID(mEglDisplay, mEglSurface, whenNsec);
+}
+
+// Swaps the EGL buffer.
+void EglWindow::swapBuffers() const {
+    eglSwapBuffers(mEglDisplay, mEglSurface);
+}
diff --git a/src/EglWindow.h b/src/EglWindow.h
new file mode 100644
index 0000000..69d0c31
--- /dev/null
+++ b/src/EglWindow.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SCREENRECORD_EGL_WINDOW_H
+#define SCREENRECORD_EGL_WINDOW_H
+
+#include <gui/BufferQueue.h>
+#include <utils/Errors.h>
+
+#include <EGL/egl.h>
+
+namespace android {
+
+/*
+ * Wraps EGL display, context, surface, config for a window surface.
+ *
+ * Not thread safe.
+ */
+class EglWindow {
+public:
+    EglWindow() :
+        mEglDisplay(EGL_NO_DISPLAY),
+        mEglContext(EGL_NO_CONTEXT),
+        mEglSurface(EGL_NO_SURFACE),
+        mEglConfig(NULL),
+        mWidth(0),
+        mHeight(0)
+        {}
+    ~EglWindow() { eglRelease(); }
+
+    // Creates an EGL window for the supplied surface.
+    status_t createWindow(const sp<IGraphicBufferProducer>& surface);
+
+    // Creates an EGL pbuffer surface.
+    status_t createPbuffer(int width, int height);
+
+    // Return width and height values (obtained from IGBP).
+    int getWidth() const { return mWidth; }
+    int getHeight() const { return mHeight; }
+
+    // Release anything we created.
+    void release() { eglRelease(); }
+
+    // Make this context current.
+    status_t makeCurrent() const;
+
+    // Sets the presentation time on the current EGL buffer.
+    void presentationTime(nsecs_t whenNsec) const;
+
+    // Swaps the EGL buffer.
+    void swapBuffers() const;
+
+private:
+    EglWindow(const EglWindow&);
+    EglWindow& operator=(const EglWindow&);
+
+    // Init display, create config and context.
+    status_t eglSetupContext(bool forPbuffer);
+    void eglRelease();
+
+    // Basic EGL goodies.
+    EGLDisplay mEglDisplay;
+    EGLContext mEglContext;
+    EGLSurface mEglSurface;
+    EGLConfig mEglConfig;
+
+    // Surface dimensions.
+    int mWidth;
+    int mHeight;
+};
+
+}; // namespace android
+
+#endif /*SCREENRECORD_EGL_WINDOW_H*/
diff --git a/src/EventQueue.cpp b/src/EventQueue.cpp
new file mode 100644
index 0000000..a107f30
--- /dev/null
+++ b/src/EventQueue.cpp
@@ -0,0 +1,59 @@
+#define LOG_TAG "VNC-EventQueue"
+#include <utils/Log.h>
+
+#include "EventQueue.h"
+
+using namespace android;
+
+void EventQueue::enqueue(const Event& event) {
+    mQueue.push(event);
+    ALOGV("enqueue: mId=%d mData=%p qlen=%zu", event.mId, event.mData, mQueue.size());
+
+    Mutex::Autolock _l(mMutex);
+    mCondition.broadcast();
+}
+
+void EventQueue::await() {
+    Mutex::Autolock _l(mMutex);
+
+    while (mRunning) {
+        ALOGV("begin wait");
+        mCondition.wait(mMutex);
+
+        ALOGV("queue active");
+        while (!mQueue.empty()) {
+            Event event = mQueue.front();
+            mQueue.pop();
+
+            mMutex.unlock();
+            for (std::vector<EventListener *>::iterator it = mListeners.begin();
+                    it != mListeners.end(); ++it) {
+                ALOGV("call listener: %p", *it);
+                (*it)->onEvent(event);
+            }
+            mMutex.lock();
+
+        }
+    }
+}
+
+void EventQueue::shutdown() {
+    Mutex::Autolock _l(mMutex);
+    flush();
+    mRunning = false;
+    mCondition.broadcast();
+}
+
+void EventQueue::flush() {
+    Mutex::Autolock _l(mMutex);
+    mQueue = {};
+}
+
+void EventQueue::addListener(EventListener *listener) {
+    mListeners.push_back(listener);
+    ALOGV("addListener: %p", listener);
+}
+
+void EventQueue::removeListener(EventListener *listener) {
+    mListeners.erase(std::remove(mListeners.begin(), mListeners.end(), listener), mListeners.end());
+}
diff --git a/src/EventQueue.h b/src/EventQueue.h
new file mode 100644
index 0000000..e926831
--- /dev/null
+++ b/src/EventQueue.h
@@ -0,0 +1,67 @@
+#ifndef MQ_H
+#define MQ_H
+
+#include <queue>
+#include <vector>
+
+#include <utils/RefBase.h>
+#include <utils/Thread.h>
+
+#define EVENT_CLIENT_CONNECT 0
+#define EVENT_CLIENT_GONE 1
+#define EVENT_BUFFER_READY 2
+
+namespace android {
+
+class Event {
+public:
+    Event(unsigned int id, void* data = 0):
+        mId(id),
+        mData(data) {}
+
+    Event(const Event& ev):
+        mId(ev.mId),
+        mData(ev.mData) {}
+
+    unsigned int mId;
+    void* mData;
+};
+
+class EventListener {
+public:
+    virtual ~EventListener() {}
+
+    virtual void onEvent(const Event& event) = 0;
+};
+
+class EventQueue : public RefBase {
+public:
+    EventQueue():
+        mRunning(true) {}
+
+    virtual void await();
+
+    virtual void shutdown();
+
+    virtual void enqueue(const Event& event);
+
+    virtual void flush();
+
+    virtual void addListener(EventListener *listener);
+
+    virtual void removeListener(EventListener *listener);
+
+    virtual ~EventQueue() { }
+
+private:
+    Mutex mMutex;
+    Condition mCondition;
+
+    std::queue<const Event> mQueue;
+    std::vector<EventListener *> mListeners;
+
+    bool mRunning;
+};
+
+};
+#endif
diff --git a/src/Program.cpp b/src/Program.cpp
new file mode 100644
index 0000000..72c11aa
--- /dev/null
+++ b/src/Program.cpp
@@ -0,0 +1,307 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "VNC"
+//#define LOG_NDEBUG 0
+#include <utils/Log.h>
+
+#include "Program.h"
+
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+
+#include <assert.h>
+
+using namespace android;
+
+// 4x4 identity matrix
+const float Program::kIdentity[] = {
+        1.0f, 0.0f, 0.0f, 0.0f,
+        0.0f, 1.0f, 0.0f, 0.0f,
+        0.0f, 0.0f, 1.0f, 0.0f,
+        0.0f, 0.0f, 0.0f, 1.0f
+};
+
+// Simple vertex shader.  Texture coord calc includes matrix for GLConsumer
+// transform.
+static const char* kVertexShader =
+        "uniform mat4 uMVPMatrix;\n"
+        "uniform mat4 uGLCMatrix;\n"
+        "attribute vec4 aPosition;\n"
+        "attribute vec4 aTextureCoord;\n"
+        "varying vec2 vTextureCoord;\n"
+        "void main() {\n"
+        "    gl_Position = uMVPMatrix * aPosition;\n"
+        "    vTextureCoord = (uGLCMatrix * aTextureCoord).xy;\n"
+        "}\n";
+
+// Trivial fragment shader for external texture.
+static const char* kExtFragmentShader =
+        "#extension GL_OES_EGL_image_external : require\n"
+        "precision mediump float;\n"
+        "varying vec2 vTextureCoord;\n"
+        "uniform samplerExternalOES uTexture;\n"
+        "void main() {\n"
+        "    gl_FragColor = texture2D(uTexture, vTextureCoord);\n"
+        "}\n";
+
+// Trivial fragment shader for mundane texture.
+static const char* kFragmentShader =
+        "precision mediump float;\n"
+        "varying vec2 vTextureCoord;\n"
+        "uniform sampler2D uTexture;\n"
+        "void main() {\n"
+        "    gl_FragColor = texture2D(uTexture, vTextureCoord);\n"
+        //"    gl_FragColor = vec4(0.2, 1.0, 0.2, 1.0);\n"
+        "}\n";
+
+status_t Program::setup(ProgramType type) {
+    ALOGV("Program::setup type=%d", type);
+    status_t err;
+
+    mProgramType = type;
+
+    GLuint program;
+    if (type == PROGRAM_TEXTURE_2D) {
+        err = createProgram(&program, kVertexShader, kFragmentShader);
+    } else {
+        err = createProgram(&program, kVertexShader, kExtFragmentShader);
+    }
+    if (err != NO_ERROR) {
+        return err;
+    }
+    assert(program != 0);
+
+    maPositionLoc = glGetAttribLocation(program, "aPosition");
+    maTextureCoordLoc = glGetAttribLocation(program, "aTextureCoord");
+    muMVPMatrixLoc = glGetUniformLocation(program, "uMVPMatrix");
+    muGLCMatrixLoc = glGetUniformLocation(program, "uGLCMatrix");
+    muTextureLoc = glGetUniformLocation(program, "uTexture");
+    if ((maPositionLoc | maTextureCoordLoc | muMVPMatrixLoc |
+            muGLCMatrixLoc | muTextureLoc) == -1) {
+        ALOGE("Attrib/uniform lookup failed: %#x", glGetError());
+        glDeleteProgram(program);
+        return UNKNOWN_ERROR;
+    }
+
+    mProgram = program;
+    return NO_ERROR;
+}
+
+void Program::release() {
+    ALOGV("Program::release");
+    if (mProgram != 0) {
+        glDeleteProgram(mProgram);
+        mProgram = 0;
+    }
+}
+
+status_t Program::createProgram(GLuint* outPgm, const char* vertexShader,
+        const char* fragmentShader) {
+    GLuint vs, fs;
+    status_t err;
+
+    err = compileShader(GL_VERTEX_SHADER, vertexShader, &vs);
+    if (err != NO_ERROR) {
+        return err;
+    }
+    err = compileShader(GL_FRAGMENT_SHADER, fragmentShader, &fs);
+    if (err != NO_ERROR) {
+        glDeleteShader(vs);
+        return err;
+    }
+
+    GLuint program;
+    err = linkShaderProgram(vs, fs, &program);
+    glDeleteShader(vs);
+    glDeleteShader(fs);
+    if (err == NO_ERROR) {
+        *outPgm = program;
+    }
+    return err;
+}
+
+status_t Program::compileShader(GLenum shaderType, const char* src,
+        GLuint* outShader) {
+    GLuint shader = glCreateShader(shaderType);
+    if (shader == 0) {
+        ALOGE("glCreateShader error: %#x", glGetError());
+        return UNKNOWN_ERROR;
+    }
+
+    glShaderSource(shader, 1, &src, NULL);
+    glCompileShader(shader);
+
+    GLint compiled = 0;
+    glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
+    if (!compiled) {
+        ALOGE("Compile of shader type %d failed", shaderType);
+        GLint infoLen = 0;
+        glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
+        if (infoLen) {
+            char* buf = new char[infoLen];
+            if (buf) {
+                glGetShaderInfoLog(shader, infoLen, NULL, buf);
+                ALOGE("Compile log: %s", buf);
+                delete[] buf;
+            }
+        }
+        glDeleteShader(shader);
+        return UNKNOWN_ERROR;
+    }
+    *outShader = shader;
+    return NO_ERROR;
+}
+
+status_t Program::linkShaderProgram(GLuint vs, GLuint fs, GLuint* outPgm) {
+    GLuint program = glCreateProgram();
+    if (program == 0) {
+        ALOGE("glCreateProgram error: %#x", glGetError());
+        return UNKNOWN_ERROR;
+    }
+
+    glAttachShader(program, vs);
+    glAttachShader(program, fs);
+    glLinkProgram(program);
+    GLint linkStatus = GL_FALSE;
+    glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
+    if (linkStatus != GL_TRUE) {
+        ALOGE("glLinkProgram failed");
+        GLint bufLength = 0;
+        glGetProgramiv(program, GL_INFO_LOG_LENGTH, &bufLength);
+        if (bufLength) {
+            char* buf = new char[bufLength];
+            if (buf) {
+                glGetProgramInfoLog(program, bufLength, NULL, buf);
+                ALOGE("Link log: %s", buf);
+                delete[] buf;
+            }
+        }
+        glDeleteProgram(program);
+        return UNKNOWN_ERROR;
+    }
+
+    *outPgm = program;
+    return NO_ERROR;
+}
+
+
+
+status_t Program::blit(GLuint texName, const float* texMatrix,
+        int32_t x, int32_t y, int32_t w, int32_t h, bool invert) const {
+    ALOGV("Program::blit %d xy=%d,%d wh=%d,%d", texName, x, y, w, h);
+
+    const float pos[] = {
+        float(x),   float(y+h),
+        float(x+w), float(y+h),
+        float(x),   float(y),
+        float(x+w), float(y),
+    };
+    const float uv[] = {
+        0.0f, 0.0f,
+        1.0f, 0.0f,
+        0.0f, 1.0f,
+        1.0f, 1.0f,
+    };
+    status_t err;
+
+    err = beforeDraw(texName, texMatrix, pos, uv, invert);
+    if (err == NO_ERROR) {
+        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+        err = afterDraw();
+    }
+    return err;
+}
+
+status_t Program::drawTriangles(GLuint texName, const float* texMatrix,
+        const float* vertices, const float* texes, size_t count) const {
+    ALOGV("Program::drawTriangles texName=%d", texName);
+
+    status_t err;
+
+    err = beforeDraw(texName, texMatrix, vertices, texes, false);
+    if (err == NO_ERROR) {
+        glDrawArrays(GL_TRIANGLES, 0, count);
+        err = afterDraw();
+    }
+    return err;
+}
+
+status_t Program::beforeDraw(GLuint texName, const float* texMatrix,
+        const float* vertices, const float* texes, bool invert) const {
+    // Create an orthographic projection matrix based on viewport size.
+    GLint vp[4];
+    glGetIntegerv(GL_VIEWPORT, vp);
+    float screenToNdc[16] = {
+        2.0f/float(vp[2]),  0.0f,               0.0f,   0.0f,
+        0.0f,               -2.0f/float(vp[3]), 0.0f,   0.0f,
+        0.0f,               0.0f,               1.0f,   0.0f,
+        -1.0f,              1.0f,               0.0f,   1.0f,
+    };
+    if (invert) {
+        screenToNdc[5] = -screenToNdc[5];
+        screenToNdc[13] = -screenToNdc[13];
+    }
+
+    glUseProgram(mProgram);
+
+    glVertexAttribPointer(maPositionLoc, 2, GL_FLOAT, GL_FALSE, 0, vertices);
+    glVertexAttribPointer(maTextureCoordLoc, 2, GL_FLOAT, GL_FALSE, 0, texes);
+    glEnableVertexAttribArray(maPositionLoc);
+    glEnableVertexAttribArray(maTextureCoordLoc);
+
+    glUniformMatrix4fv(muMVPMatrixLoc, 1, GL_FALSE, screenToNdc);
+    glUniformMatrix4fv(muGLCMatrixLoc, 1, GL_FALSE, texMatrix);
+
+    glActiveTexture(GL_TEXTURE0);
+
+    switch (mProgramType) {
+    case PROGRAM_EXTERNAL_TEXTURE:
+        glBindTexture(GL_TEXTURE_EXTERNAL_OES, texName);
+        break;
+    case PROGRAM_TEXTURE_2D:
+        glBindTexture(GL_TEXTURE_2D, texName);
+        break;
+    default:
+        ALOGE("unexpected program type %d", mProgramType);
+        return UNKNOWN_ERROR;
+    }
+
+    glUniform1i(muTextureLoc, 0);
+
+    GLenum glErr;
+    if ((glErr = glGetError()) != GL_NO_ERROR) {
+        ALOGE("GL error before draw: %#x", glErr);
+        glDisableVertexAttribArray(maPositionLoc);
+        glDisableVertexAttribArray(maTextureCoordLoc);
+        return UNKNOWN_ERROR;
+    }
+
+    return NO_ERROR;
+}
+
+status_t Program::afterDraw() const {
+    glDisableVertexAttribArray(maPositionLoc);
+    glDisableVertexAttribArray(maTextureCoordLoc);
+
+    GLenum glErr;
+    if ((glErr = glGetError()) != GL_NO_ERROR) {
+        ALOGE("GL error after draw: %#x", glErr);
+        return UNKNOWN_ERROR;
+    }
+
+    return NO_ERROR;
+}
diff --git a/src/Program.h b/src/Program.h
new file mode 100644
index 0000000..558be8d
--- /dev/null
+++ b/src/Program.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SCREENRECORD_PROGRAM_H
+#define SCREENRECORD_PROGRAM_H
+
+#include <utils/Errors.h>
+
+#include <EGL/egl.h>
+#include <GLES2/gl2.h>
+
+namespace android {
+
+/*
+ * Utility class for GLES rendering.
+ *
+ * Not thread-safe.
+ */
+class Program {
+public:
+    enum ProgramType { PROGRAM_UNKNOWN=0, PROGRAM_EXTERNAL_TEXTURE,
+            PROGRAM_TEXTURE_2D };
+
+    Program() :
+        mProgramType(PROGRAM_UNKNOWN),
+        mProgram(0),
+        maPositionLoc(0),
+        maTextureCoordLoc(0),
+        muMVPMatrixLoc(0),
+        muGLCMatrixLoc(0),
+        muTextureLoc(0)
+        {}
+    ~Program() { release(); }
+
+    // Initialize the program for use with the specified texture type.
+    status_t setup(ProgramType type);
+
+    // Release the program and associated resources.
+    void release();
+
+    // Blit the specified texture to { x, y, x+w, y+h }.  Inverts the
+    // content if "invert" is set.
+    status_t blit(GLuint texName, const float* texMatrix,
+            int32_t x, int32_t y, int32_t w, int32_t h,
+            bool invert = false) const;
+
+    // Draw a number of triangles.
+    status_t drawTriangles(GLuint texName, const float* texMatrix,
+            const float* vertices, const float* texes, size_t count) const;
+
+    static const float kIdentity[];
+
+private:
+    Program(const Program&);
+    Program& operator=(const Program&);
+
+    // Common code for draw functions.
+    status_t beforeDraw(GLuint texName, const float* texMatrix,
+            const float* vertices, const float* texes, bool invert) const;
+    status_t afterDraw() const;
+
+    // GLES 2 shader utilities.
+    status_t createProgram(GLuint* outPgm, const char* vertexShader,
+            const char* fragmentShader);
+    static status_t compileShader(GLenum shaderType, const char* src,
+            GLuint* outShader);
+    static status_t linkShaderProgram(GLuint vs, GLuint fs, GLuint* outPgm);
+
+    ProgramType mProgramType;
+    GLuint mProgram;
+
+    GLint maPositionLoc;
+    GLint maTextureCoordLoc;
+    GLint muMVPMatrixLoc;
+    GLint muGLCMatrixLoc;
+    GLint muTextureLoc;
+};
+
+}; // namespace android
+
+#endif /*SCREENRECORD_PROGRAM_H*/
diff --git a/src/README b/src/README
new file mode 100644
index 0000000..e6cbb2b
--- /dev/null
+++ b/src/README
@@ -0,0 +1,2 @@
+- The surfaceflinger method is present from version 2.3.X and should be supported by all devices.
+- It connects with the surfaceflinger service through a Binder interface.
diff --git a/src/VNCFlinger.cpp b/src/VNCFlinger.cpp
new file mode 100644
index 0000000..a55b868
--- /dev/null
+++ b/src/VNCFlinger.cpp
@@ -0,0 +1,165 @@
+#define LOG_TAG "VNCFlinger"
+#include <utils/Log.h>
+
+#include <binder/IPCThreadState.h>
+#include <binder/ProcessState.h>
+#include <binder/IServiceManager.h>
+
+#include <gui/ISurfaceComposer.h>
+#include <gui/SurfaceComposerClient.h>
+#include <gui/IGraphicBufferProducer.h>
+
+#include "VNCFlinger.h"
+
+using namespace android;
+
+EventQueue *VNCFlinger::sQueue = new EventQueue();
+
+status_t VNCFlinger::start() {
+    Mutex::Autolock _l(mMutex);
+
+    status_t err = setup_l();
+    if (err != NO_ERROR) {
+        ALOGE("Failed to start VNCFlinger: err=%d", err);
+        return err;
+    }
+
+    ALOGD("VNCFlinger is running!");
+
+    rfbRunEventLoop(mVNCScreen, -1, true);
+    sQueue->await();
+
+    release_l();
+    return NO_ERROR;
+}
+
+status_t VNCFlinger::setup_l() {
+
+    status_t err = NO_ERROR;
+
+    mMainDpy = SurfaceComposerClient::getBuiltInDisplay(
+            ISurfaceComposer::eDisplayIdMain);
+    err = SurfaceComposerClient::getDisplayInfo(mMainDpy, &mMainDpyInfo);
+    if (err != NO_ERROR) {
+        ALOGE("Unable to get display characteristics\n");
+        return err;
+    }
+
+    bool rotated = VirtualDisplay::isDeviceRotated(mMainDpyInfo.orientation);
+    if (mWidth == 0) {
+        mWidth = rotated ? mMainDpyInfo.h : mMainDpyInfo.w;
+    }
+    if (mHeight == 0) {
+        mHeight = rotated ? mMainDpyInfo.w : mMainDpyInfo.h;
+    }
+
+    ALOGD("Display dimensions: %dx%d rotated=%d", mWidth, mHeight, rotated);
+
+    sQueue->addListener(this);
+
+    mVirtualDisplay = new VirtualDisplay();
+
+    mVNCBuf = new uint8_t[mWidth * mHeight * 4];
+
+    rfbLog = VNCFlinger::rfbLogger;
+    rfbErr = VNCFlinger::rfbLogger;
+
+    // 32-bit color
+    mVNCScreen = rfbGetScreen(&mArgc, mArgv, mWidth, mHeight, 8, 3, 4);
+    if (mVNCScreen == NULL) {
+        ALOGE("Unable to create VNCScreen");
+        return NO_INIT;
+    }
+
+    mVNCScreen->desktopName = "VNCFlinger";
+    mVNCScreen->frameBuffer = (char *)mVNCBuf;
+    mVNCScreen->alwaysShared = TRUE;
+    mVNCScreen->httpDir = NULL;
+    mVNCScreen->port = VNC_PORT;
+    mVNCScreen->newClientHook = (rfbNewClientHookPtr) VNCFlinger::onNewClient;
+    mVNCScreen->serverFormat.trueColour = true;
+    mVNCScreen->serverFormat.bitsPerPixel = 32;
+    mVNCScreen->handleEventsEagerly = true;
+    mVNCScreen->deferUpdateTime = 5;
+
+    rfbInitServer(mVNCScreen);
+
+    /* Mark as dirty since we haven't sent any updates at all yet. */
+    rfbMarkRectAsModified(mVNCScreen, 0, 0, mWidth, mHeight);
+
+    return err;
+}
+
+void VNCFlinger::release_l() {
+    sQueue->removeListener(this);
+    mVirtualDisplay.clear();
+
+    ALOGD("VNCFlinger released");
+}
+
+status_t VNCFlinger::stop() {
+    Mutex::Autolock _l(mMutex);
+    sQueue->shutdown();
+
+    return NO_ERROR;
+}
+
+void VNCFlinger::onEvent(const Event& event) {
+
+    ALOGV("onEvent: mId=%d mData=%p", event.mId, event.mData);
+
+    switch(event.mId) {
+        case EVENT_CLIENT_CONNECT:
+            if (mClientCount == 0) {
+                mVirtualDisplay->start(mMainDpyInfo, sQueue);
+            }
+            mClientCount++;
+
+            ALOGI("Client connected (%zu)", mClientCount);
+            break;
+
+        case EVENT_CLIENT_GONE:
+            if (mClientCount > 0) {
+                mClientCount--;
+                if (mClientCount == 0) {
+                    mVirtualDisplay->stop();
+                }
+            }
+
+            ALOGI("Client disconnected (%zu)", mClientCount);
+            break;
+
+        case EVENT_BUFFER_READY:
+            //mVNCScreen->frameBuffer = (char *) event.mData;
+            memcpy(mVNCBuf, (uint8_t *) event.mData, mWidth * mHeight * 4);
+            rfbMarkRectAsModified(mVNCScreen, 0, 0, mWidth, mHeight);
+            break;
+
+        default:
+            ALOGE("Unhandled event: %d", event.mId);
+            break;
+    }
+}
+
+ClientGoneHookPtr VNCFlinger::onClientGone(rfbClientPtr /* cl */) {
+    ALOGV("onClientGone");
+    sQueue->enqueue(Event(EVENT_CLIENT_GONE));
+    return 0;
+}
+
+enum rfbNewClientAction VNCFlinger::onNewClient(rfbClientPtr cl) {
+    ALOGV("onNewClient");
+    cl->clientGoneHook = (ClientGoneHookPtr) VNCFlinger::onClientGone;
+    sQueue->enqueue(Event(EVENT_CLIENT_CONNECT));
+    return RFB_CLIENT_ACCEPT;
+}
+
+void VNCFlinger::rfbLogger(const char *format, ...) {
+    va_list args;
+    char buf[256];
+
+    va_start(args, format);
+    vsprintf(buf, format, args);
+    ALOGI("%s", buf);
+    va_end(args);
+}
diff --git a/src/VNCFlinger.h b/src/VNCFlinger.h
new file mode 100644
index 0000000..45057b5
--- /dev/null
+++ b/src/VNCFlinger.h
@@ -0,0 +1,58 @@
+#ifndef VNCFLINGER_H
+#define VNCFLINGER_H
+
+#include "EventQueue.h"
+#include "VirtualDisplay.h"
+
+#include <ui/DisplayInfo.h>
+
+#include "rfb/rfb.h"
+
+#define VNC_PORT 5901
+
+namespace android {
+
+class VNCFlinger : public EventListener {
+public:
+    VNCFlinger(int argc, char **argv) :
+            mArgc(argc),
+            mArgv(argv),
+            mClientCount(0) {
+    }
+
+    virtual void onEvent(const Event& event);
+
+    virtual status_t start();
+    virtual status_t stop();
+
+    static EventQueue *sQueue;
+
+private:
+    virtual status_t setup_l();
+    virtual void release_l();
+
+    static ClientGoneHookPtr onClientGone(rfbClientPtr cl);
+    static enum rfbNewClientAction onNewClient(rfbClientPtr cl);
+    static void rfbLogger(const char *format, ...);
+        
+    rfbScreenInfoPtr mVNCScreen;
+    uint8_t *mVNCBuf;
+
+    uint32_t mWidth, mHeight;
+    bool mRotate;
+
+    sp<IBinder> mMainDpy;
+    DisplayInfo mMainDpyInfo;
+    
+    Mutex mMutex;
+
+    sp<VirtualDisplay> mVirtualDisplay;
+
+    int mArgc;
+    char **mArgv;
+
+    size_t mClientCount;
+};
+
+};
+#endif
diff --git a/src/VirtualDisplay.cpp b/src/VirtualDisplay.cpp
new file mode 100644
index 0000000..369d271
--- /dev/null
+++ b/src/VirtualDisplay.cpp
@@ -0,0 +1,297 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "VNC-VirtualDisplay"
+//#define LOG_NDEBUG 0
+#include <utils/Log.h>
+
+#include <binder/IPCThreadState.h>
+#include <binder/ProcessState.h>
+
+#include <gui/SurfaceComposerClient.h>
+
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+#include <GLES3/gl3.h>
+
+#include <ui/Rect.h>
+
+#include "VirtualDisplay.h"
+
+using namespace android;
+
+static const int kGlBytesPerPixel = 4;      // GL_RGBA
+
+
+/*
+ * Returns "true" if the device is rotated 90 degrees.
+ */
+bool VirtualDisplay::isDeviceRotated(int orientation) {
+    return orientation != DISPLAY_ORIENTATION_0 &&
+            orientation != DISPLAY_ORIENTATION_180;
+}
+
+/*
+ * Sets the display projection, based on the display dimensions, video size,
+ * and device orientation.
+ */
+status_t VirtualDisplay::setDisplayProjection(const sp<IBinder>& dpy,
+        const DisplayInfo& mMainDpyInfo) {
+    status_t err;
+
+    // Set the region of the layer stack we're interested in, which in our
+    // case is "all of it".  If the app is rotated (so that the width of the
+    // app is based on the height of the display), reverse width/height.
+    bool deviceRotated = isDeviceRotated(mMainDpyInfo.orientation);
+    uint32_t sourceWidth, sourceHeight;
+    if (!deviceRotated) {
+        sourceWidth = mMainDpyInfo.w;
+        sourceHeight = mMainDpyInfo.h;
+    } else {
+        ALOGV("using rotated width/height");
+        sourceHeight = mMainDpyInfo.w;
+        sourceWidth = mMainDpyInfo.h;
+    }
+    Rect layerStackRect(sourceWidth, sourceHeight);
+
+    // We need to preserve the aspect ratio of the display.
+    float displayAspect = (float) sourceHeight / (float) sourceWidth;
+
+
+    // Set the way we map the output onto the display surface (which will
+    // be e.g. 1280x720 for a 720p video).  The rect is interpreted
+    // post-rotation, so if the display is rotated 90 degrees we need to
+    // "pre-rotate" it by flipping width/height, so that the orientation
+    // adjustment changes it back.
+    //
+    // We might want to encode a portrait display as landscape to use more
+    // of the screen real estate.  (If players respect a 90-degree rotation
+    // hint, we can essentially get a 720x1280 video instead of 1280x720.)
+    // In that case, we swap the configured video width/height and then
+    // supply a rotation value to the display projection.
+    uint32_t videoWidth, videoHeight;
+    uint32_t outWidth, outHeight;
+   if (!mRotate) {
+        videoWidth = mWidth;
+        videoHeight = mHeight;
+    } else {
+        videoWidth = mHeight;
+        videoHeight = mWidth;
+    }
+    if (videoHeight > (uint32_t)(videoWidth * displayAspect)) {
+        // limited by narrow width; reduce height
+        outWidth = videoWidth;
+        outHeight = (uint32_t)(videoWidth * displayAspect);
+    } else {
+        // limited by short height; restrict width
+        outHeight = videoHeight;
+        outWidth = (uint32_t)(videoHeight / displayAspect);
+    }
+    uint32_t offX, offY;
+    offX = (videoWidth - outWidth) / 2;
+    offY = (videoHeight - outHeight) / 2;
+    Rect displayRect(offX, offY, offX + outWidth, offY + outHeight);
+
+    if (mRotate) {
+        printf("Rotated content area is %ux%u at offset x=%d y=%d\n",
+                outHeight, outWidth, offY, offX);
+    } else {
+        printf("Content area is %ux%u at offset x=%d y=%d\n",
+                outWidth, outHeight, offX, offY);
+    }
+
+    SurfaceComposerClient::setDisplayProjection(dpy,
+            mRotate ? DISPLAY_ORIENTATION_90 : DISPLAY_ORIENTATION_0,
+            layerStackRect, displayRect);
+    return NO_ERROR;
+}
+
+status_t VirtualDisplay::start(const DisplayInfo& mainDpyInfo, EventQueue *queue) {
+
+    Mutex::Autolock _l(mMutex);
+
+    mQueue = queue;
+
+    mRotate = isDeviceRotated(mainDpyInfo.orientation);
+    mWidth = mRotate ? mainDpyInfo.h : mainDpyInfo.w;
+    mHeight = mRotate ? mainDpyInfo.w : mainDpyInfo.h;
+
+    sp<ProcessState> self = ProcessState::self();
+    self->startThreadPool();
+
+    run("vnc-virtualdisplay");
+    
+    mState = INIT;
+    while (mState == INIT) {
+        mStartCond.wait(mMutex);
+    }
+  
+    if (mThreadResult != NO_ERROR) {
+        ALOGE("Failed to start VDS thread: err=%d", mThreadResult);
+        return mThreadResult;
+    }
+    assert(mState == RUNNING);
+  
+    mDpy = SurfaceComposerClient::createDisplay(
+            String8("VNCFlinger"), false /*secure*/);
+
+    SurfaceComposerClient::openGlobalTransaction();
+    SurfaceComposerClient::setDisplaySurface(mDpy, mProducer);
+    setDisplayProjection(mDpy, mainDpyInfo);
+    SurfaceComposerClient::setDisplayLayerStack(mDpy, 0);    // default stack
+    SurfaceComposerClient::closeGlobalTransaction();
+
+    ALOGV("VirtualDisplay::start successful");
+    return NO_ERROR;
+}
+
+status_t VirtualDisplay::stop() {
+    Mutex::Autolock _l(mMutex);
+    mState = STOPPING;
+    mEventCond.signal();
+    return NO_ERROR;
+}
+  
+bool VirtualDisplay::threadLoop() {
+    Mutex::Autolock _l(mMutex);
+
+    mThreadResult = setup_l();
+
+    if (mThreadResult != NO_ERROR) {
+        ALOGW("Aborting VDS thread");
+        mState = STOPPED;
+        release_l();
+        mStartCond.broadcast();
+        return false;
+    }
+
+    ALOGV("VDS thread running");
+    mState = RUNNING;
+    mStartCond.broadcast();
+
+    while (mState == RUNNING) {
+        mEventCond.wait(mMutex);
+        ALOGD("Awake, frame available");
+        void* ptr = processFrame_l();
+        const Event ev(EVENT_BUFFER_READY, ptr);
+        mQueue->enqueue(ev);
+    }
+
+    ALOGV("VDS thread stopping");
+    release_l();
+    mState = STOPPED;
+    return false;       // stop
+}
+
+status_t VirtualDisplay::setup_l() {
+    status_t err;
+
+    err = mEglWindow.createPbuffer(mWidth, mHeight);
+    if (err != NO_ERROR) {
+        return err;
+    }
+    mEglWindow.makeCurrent();
+
+    glViewport(0, 0, mWidth, mHeight);
+    glDisable(GL_DEPTH_TEST);
+    glDisable(GL_CULL_FACE);
+
+    // Shader for rendering the external texture.
+    err = mExtTexProgram.setup(Program::PROGRAM_EXTERNAL_TEXTURE);
+    if (err != NO_ERROR) {
+        return err;
+    }
+
+    // Input side (buffers from virtual display).
+    glGenTextures(1, &mExtTextureName);
+    if (mExtTextureName == 0) {
+        ALOGE("glGenTextures failed: %#x", glGetError());
+        return UNKNOWN_ERROR;
+    }
+
+    mBufSize = mWidth * mHeight * kGlBytesPerPixel;
+    
+    // pixel buffer for image copy
+    mPBO = new GLuint[NUM_PBO];
+    glGenBuffers(NUM_PBO, mPBO);
+
+    for (unsigned int i = 0; i < NUM_PBO; i++) {
+        glBindBuffer(GL_PIXEL_PACK_BUFFER, mPBO[i]);
+        glBufferData(GL_PIXEL_PACK_BUFFER, mBufSize, 0, GL_DYNAMIC_READ);
+        glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
+    }
+
+    sp<IGraphicBufferConsumer> consumer;
+    BufferQueue::createBufferQueue(&mProducer, &consumer);
+    mGlConsumer = new GLConsumer(consumer, mExtTextureName,
+                GL_TEXTURE_EXTERNAL_OES, true, false);
+    mGlConsumer->setName(String8("virtual display"));
+    mGlConsumer->setDefaultBufferSize(mWidth, mHeight);
+    mProducer->setMaxDequeuedBufferCount(4);
+    mGlConsumer->setConsumerUsageBits(GRALLOC_USAGE_HW_TEXTURE);
+
+    mGlConsumer->setFrameAvailableListener(this);
+
+    ALOGD("VirtualDisplay::setup_l OK");
+    return NO_ERROR;
+}
+
+void* VirtualDisplay::processFrame_l() {
+    ALOGD("processFrame_l\n");
+
+    float texMatrix[16];
+    mGlConsumer->updateTexImage();
+    mGlConsumer->getTransformMatrix(texMatrix);
+
+    // The data is in an external texture, so we need to render it to the
+    // pbuffer to get access to RGB pixel data.  We also want to flip it
+    // upside-down for easy conversion to a bitmap.
+    int width = mEglWindow.getWidth();
+    int height = mEglWindow.getHeight();
+    mExtTexProgram.blit(mExtTextureName, texMatrix, 0, 0, mWidth, mHeight, true);
+
+    GLenum glErr;
+    glBindBuffer(GL_PIXEL_PACK_BUFFER, mPBO[mIndex]);
+    glReadPixels(0, 0, mWidth, mHeight, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+    if ((glErr = glGetError()) != GL_NO_ERROR) {
+        ALOGE("glReadPixels failed: %#x", glErr);
+        return NULL;
+    }
+
+    glBindBuffer(GL_PIXEL_PACK_BUFFER, mPBO[mIndex]);
+    void* ptr = glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, mBufSize, GL_MAP_READ_BIT);
+    glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
+    glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
+
+    mIndex = (mIndex + 1) % NUM_PBO; 
+    return ptr;
+}
+
+void VirtualDisplay::release_l() {
+    ALOGD("release_l");
+    mGlConsumer.clear();
+    mProducer.clear();
+    mExtTexProgram.release();
+    mEglWindow.release();
+    SurfaceComposerClient::destroyDisplay(mDpy);
+}
+
+// Callback; executes on arbitrary thread.
+void VirtualDisplay::onFrameAvailable(const BufferItem& item) {
+    Mutex::Autolock _l(mMutex);
+    mEventCond.signal();
+    ALOGD("mTimestamp=%ld mFrameNumber=%ld", item.mTimestamp, item.mFrameNumber);
+}
diff --git a/src/VirtualDisplay.h b/src/VirtualDisplay.h
new file mode 100644
index 0000000..ccfb718
--- /dev/null
+++ b/src/VirtualDisplay.h
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef VDS_H
+#define VDS_H
+
+#include "EventQueue.h"
+#include "Program.h"
+#include "EglWindow.h"
+
+#include <gui/BufferQueue.h>
+#include <gui/GLConsumer.h>
+#include <gui/IGraphicBufferProducer.h>
+#include <ui/DisplayInfo.h>
+#include <utils/Thread.h>
+
+
+#define NUM_PBO 2
+
+namespace android {
+
+/*
+ * Support for "frames" output format.
+ */
+class VirtualDisplay : public GLConsumer::FrameAvailableListener, Thread {
+public:
+    VirtualDisplay() : Thread(false),
+        mThreadResult(UNKNOWN_ERROR),
+        mState(UNINITIALIZED),
+        mIndex(0)
+        {}
+
+    // Create an "input surface", similar in purpose to a MediaCodec input
+    // surface, that the virtual display can send buffers to.  Also configures
+    // EGL with a pbuffer surface on the current thread.
+    status_t start(const DisplayInfo& mainDpyInfo, EventQueue *queue);
+
+    status_t stop();
+
+    static bool isDeviceRotated(int orientation);
+
+private:
+    VirtualDisplay(const VirtualDisplay&);
+    VirtualDisplay& operator=(const VirtualDisplay&);
+
+    // Destruction via RefBase.
+    virtual ~VirtualDisplay() {
+        assert(mState == UNINITIALIZED || mState == STOPPED);
+    }
+
+    virtual status_t setDisplayProjection(const sp<IBinder>& dpy,
+            const DisplayInfo& mainDpyInfo);
+
+    // (overrides GLConsumer::FrameAvailableListener method)
+    virtual void onFrameAvailable(const BufferItem& item);
+
+    // (overrides Thread method)
+    virtual bool threadLoop();
+
+    // One-time setup (essentially object construction on the overlay thread).
+    status_t setup_l();
+
+    // Release all resources held.
+    void release_l();
+
+    // Process a frame received from the virtual display.
+    void* processFrame_l();
+
+    uint32_t mHeight, mWidth;
+    bool mRotate;
+
+    EventQueue *mQueue;
+
+    // Used to wait for the FrameAvailableListener callback.
+    Mutex mMutex;
+
+    // Initialization gate.
+    Condition mStartCond;
+
+    // Thread status, mostly useful during startup.
+    status_t mThreadResult;
+    // Overlay thread state.  States advance from left to right; object may
+    // not be restarted.
+    enum { UNINITIALIZED, INIT, RUNNING, STOPPING, STOPPED } mState;
+
+    // Event notification.  Overlay thread sleeps on this until a frame
+    // arrives or it's time to shut down.
+    Condition mEventCond;
+
+    // Producer side of queue, passed into the virtual display.
+    // The consumer end feeds into our GLConsumer.
+    sp<IGraphicBufferProducer> mProducer;
+
+    // This receives frames from the virtual display and makes them available
+    // as an external texture.
+    sp<GLConsumer> mGlConsumer;
+
+    // EGL display / context / surface.
+    EglWindow mEglWindow;
+
+    // GL rendering support.
+    Program mExtTexProgram;
+
+    // External texture, updated by GLConsumer.
+    GLuint mExtTextureName;
+
+    // Pixel data buffers.
+    size_t mBufSize;
+    GLuint* mPBO;
+    unsigned int mIndex;
+
+    sp<IBinder> mDpy;
+};
+
+}; // namespace android
+
+#endif /* VDS_H */
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100644
index 0000000..0223f78
--- /dev/null
+++ b/src/main.cpp
@@ -0,0 +1,8 @@
+#include "VNCFlinger.h"
+
+using namespace android;
+
+int main(int argc, char **argv) {
+    VNCFlinger flinger(argc, argv);
+    flinger.start();
+}