am ee37c3a8: am 63b64337: Add larger RSA key sizes

* commit 'ee37c3a845ea1de5508ad3059457117170a8e5d5':
  Add larger RSA key sizes
diff --git a/modules/camera/Android.mk b/modules/camera/Android.mk
index eebffc1..823fe17 100644
--- a/modules/camera/Android.mk
+++ b/modules/camera/Android.mk
@@ -26,6 +26,7 @@
 LOCAL_SRC_FILES := \
 	CameraHAL.cpp \
 	Camera.cpp \
+	Stream.cpp \
 
 LOCAL_SHARED_LIBRARIES := \
 	libcamera_metadata \
diff --git a/modules/camera/Camera.cpp b/modules/camera/Camera.cpp
index 203b772..21ac232 100644
--- a/modules/camera/Camera.cpp
+++ b/modules/camera/Camera.cpp
@@ -18,6 +18,7 @@
 #include <pthread.h>
 #include <hardware/camera3.h>
 #include "CameraHAL.h"
+#include "Stream.h"
 
 //#define LOG_NDEBUG 0
 #define LOG_TAG "Camera"
@@ -25,6 +26,7 @@
 
 #define ATRACE_TAG (ATRACE_TAG_CAMERA | ATRACE_TAG_HAL)
 #include <cutils/trace.h>
+#include "ScopedTrace.h"
 
 #include "Camera.h"
 
@@ -43,7 +45,9 @@
 Camera::Camera(int id)
   : mId(id),
     mBusy(false),
-    mCallbackOps(NULL)
+    mCallbackOps(NULL),
+    mStreams(NULL),
+    mNumStreams(0)
 {
     pthread_mutex_init(&mMutex,
                        NULL); // No pthread mutex attributes.
@@ -62,11 +66,10 @@
 int Camera::open(const hw_module_t *module, hw_device_t **device)
 {
     ALOGI("%s:%d: Opening camera device", __func__, mId);
-    ATRACE_BEGIN(__func__);
+    CAMTRACE_CALL();
     pthread_mutex_lock(&mMutex);
     if (mBusy) {
         pthread_mutex_unlock(&mMutex);
-        ATRACE_END();
         ALOGE("%s:%d: Error! Camera device already opened", __func__, mId);
         return -EBUSY;
     }
@@ -77,18 +80,16 @@
     *device = &mDevice.common;
 
     pthread_mutex_unlock(&mMutex);
-    ATRACE_END();
     return 0;
 }
 
 int Camera::close()
 {
     ALOGI("%s:%d: Closing camera device", __func__, mId);
-    ATRACE_BEGIN(__func__);
+    CAMTRACE_CALL();
     pthread_mutex_lock(&mMutex);
     if (!mBusy) {
         pthread_mutex_unlock(&mMutex);
-        ATRACE_END();
         ALOGE("%s:%d: Error! Camera device not open", __func__, mId);
         return -EINVAL;
     }
@@ -97,7 +98,6 @@
     mBusy = false;
 
     pthread_mutex_unlock(&mMutex);
-    ATRACE_END();
     return 0;
 }
 
@@ -108,18 +108,170 @@
     return 0;
 }
 
-int Camera::configureStreams(camera3_stream_configuration_t *stream_list)
+int Camera::configureStreams(camera3_stream_configuration_t *stream_config)
 {
-    ALOGV("%s:%d: stream_list=%p", __func__, mId, stream_list);
-    // TODO: validate input, create internal stream representations
+    camera3_stream_t *astream;
+    Stream **newStreams = NULL;
+
+    CAMTRACE_CALL();
+    ALOGV("%s:%d: stream_config=%p", __func__, mId, stream_config);
+
+    if (stream_config == NULL) {
+        ALOGE("%s:%d: NULL stream configuration array", __func__, mId);
+        return -EINVAL;
+    }
+    if (stream_config->num_streams == 0) {
+        ALOGE("%s:%d: Empty stream configuration array", __func__, mId);
+        return -EINVAL;
+    }
+
+    // Create new stream array
+    newStreams = new Stream*[stream_config->num_streams];
+    ALOGV("%s:%d: Number of Streams: %d", __func__, mId,
+            stream_config->num_streams);
+
+    pthread_mutex_lock(&mMutex);
+
+    // Mark all current streams unused for now
+    for (int i = 0; i < mNumStreams; i++)
+        mStreams[i]->mReuse = false;
+    // Fill new stream array with reused streams and new streams
+    for (int i = 0; i < stream_config->num_streams; i++) {
+        astream = stream_config->streams[i];
+        if (astream->max_buffers > 0)
+            newStreams[i] = reuseStream(astream);
+        else
+            newStreams[i] = new Stream(mId, astream);
+
+        if (newStreams[i] == NULL) {
+            ALOGE("%s:%d: Error processing stream %d", __func__, mId, i);
+            goto err_out;
+        }
+        astream->priv = newStreams[i];
+    }
+
+    // Verify the set of streams in aggregate
+    if (!isValidStreamSet(newStreams, stream_config->num_streams)) {
+        ALOGE("%s:%d: Invalid stream set", __func__, mId);
+        goto err_out;
+    }
+
+    // Set up all streams (calculate usage/max_buffers for each)
+    setupStreams(newStreams, stream_config->num_streams);
+
+    // Destroy all old streams and replace stream array with new one
+    destroyStreams(mStreams, mNumStreams);
+    mStreams = newStreams;
+    mNumStreams = stream_config->num_streams;
+
+    pthread_mutex_unlock(&mMutex);
     return 0;
+
+err_out:
+    // Clean up temporary streams, preserve existing mStreams/mNumStreams
+    destroyStreams(newStreams, stream_config->num_streams);
+    pthread_mutex_unlock(&mMutex);
+    return -EINVAL;
+}
+
+void Camera::destroyStreams(Stream **streams, int count)
+{
+    if (streams == NULL)
+        return;
+    for (int i = 0; i < count; i++) {
+        // Only destroy streams that weren't reused
+        if (streams[i] != NULL && !streams[i]->mReuse)
+            delete streams[i];
+    }
+    delete [] streams;
+}
+
+Stream *Camera::reuseStream(camera3_stream_t *astream)
+{
+    Stream *priv = reinterpret_cast<Stream*>(astream->priv);
+    // Verify the re-used stream's parameters match
+    if (!priv->isValidReuseStream(mId, astream)) {
+        ALOGE("%s:%d: Mismatched parameter in reused stream", __func__, mId);
+        return NULL;
+    }
+    // Mark stream to be reused
+    priv->mReuse = true;
+    return priv;
+}
+
+bool Camera::isValidStreamSet(Stream **streams, int count)
+{
+    int inputs = 0;
+    int outputs = 0;
+
+    if (streams == NULL) {
+        ALOGE("%s:%d: NULL stream configuration streams", __func__, mId);
+        return false;
+    }
+    if (count == 0) {
+        ALOGE("%s:%d: Zero count stream configuration streams", __func__, mId);
+        return false;
+    }
+    // Validate there is at most one input stream and at least one output stream
+    for (int i = 0; i < count; i++) {
+        // A stream may be both input and output (bidirectional)
+        if (streams[i]->isInputType())
+            inputs++;
+        if (streams[i]->isOutputType())
+            outputs++;
+    }
+    if (outputs < 1) {
+        ALOGE("%s:%d: Stream config must have >= 1 output", __func__, mId);
+        return false;
+    }
+    if (inputs > 1) {
+        ALOGE("%s:%d: Stream config must have <= 1 input", __func__, mId);
+        return false;
+    }
+    // TODO: check for correct number of Bayer/YUV/JPEG/Encoder streams
+    return true;
+}
+
+void Camera::setupStreams(Stream **streams, int count)
+{
+    /*
+     * This is where the HAL has to decide internally how to handle all of the
+     * streams, and then produce usage and max_buffer values for each stream.
+     * Note, the stream array has been checked before this point for ALL invalid
+     * conditions, so it must find a successful configuration for this stream
+     * array.  The HAL may not return an error from this point.
+     *
+     * In this demo HAL, we just set all streams to be the same dummy values;
+     * real implementations will want to avoid USAGE_SW_{READ|WRITE}_OFTEN.
+     */
+    for (int i = 0; i < count; i++) {
+        uint32_t usage = 0;
+
+        if (streams[i]->isOutputType())
+            usage |= GRALLOC_USAGE_SW_WRITE_OFTEN |
+                     GRALLOC_USAGE_HW_CAMERA_WRITE;
+        if (streams[i]->isInputType())
+            usage |= GRALLOC_USAGE_SW_READ_OFTEN |
+                     GRALLOC_USAGE_HW_CAMERA_READ;
+
+        streams[i]->setUsage(usage);
+        streams[i]->setMaxBuffers(1);
+    }
 }
 
 int Camera::registerStreamBuffers(const camera3_stream_buffer_set_t *buf_set)
 {
     ALOGV("%s:%d: buffer_set=%p", __func__, mId, buf_set);
-    // TODO: register buffers with hardware
-    return 0;
+    if (buf_set == NULL) {
+        ALOGE("%s:%d: NULL buffer set", __func__, mId);
+        return -EINVAL;
+    }
+    if (buf_set->stream == NULL) {
+        ALOGE("%s:%d: NULL stream handle", __func__, mId);
+        return -EINVAL;
+    }
+    Stream *stream = reinterpret_cast<Stream*>(buf_set->stream->priv);
+    return stream->registerBuffers(buf_set);
 }
 
 const camera_metadata_t* Camera::constructDefaultRequestSettings(int type)
@@ -132,16 +284,14 @@
 int Camera::processCaptureRequest(camera3_capture_request_t *request)
 {
     ALOGV("%s:%d: request=%p", __func__, mId, request);
-    ATRACE_BEGIN(__func__);
+    CAMTRACE_CALL();
 
     if (request == NULL) {
         ALOGE("%s:%d: NULL request recieved", __func__, mId);
-        ATRACE_END();
         return -EINVAL;
     }
 
     // TODO: verify request; submit request to hardware
-    ATRACE_END();
     return 0;
 }
 
@@ -153,7 +303,7 @@
 
 void Camera::dump(int fd)
 {
-    ALOGV("%s:%d: Dumping to fd %d", fd);
+    ALOGV("%s:%d: Dumping to fd %d", __func__, mId, fd);
     // TODO: dprintf all relevant state to fd
 }
 
diff --git a/modules/camera/Camera.h b/modules/camera/Camera.h
index f2ad093..c43e207 100644
--- a/modules/camera/Camera.h
+++ b/modules/camera/Camera.h
@@ -20,6 +20,7 @@
 #include <pthread.h>
 #include <hardware/hardware.h>
 #include <hardware/camera3.h>
+#include "Stream.h"
 
 namespace default_camera_hal {
 // Camera represents a physical camera on a device.
@@ -50,6 +51,15 @@
         camera3_device_t mDevice;
 
     private:
+        // Reuse a stream already created by this device
+        Stream *reuseStream(camera3_stream_t *astream);
+        // Destroy all streams in a stream array, and the array itself
+        void destroyStreams(Stream **array, int count);
+        // Verify a set of streams is valid in aggregate
+        bool isValidStreamSet(Stream **array, int count);
+        // Calculate usage and max_bufs of each stream
+        void setupStreams(Stream **array, int count);
+
         // Identifier used by framework to distinguish cameras
         const int mId;
         // Busy flag indicates camera is in use
@@ -60,6 +70,10 @@
         const camera3_callback_ops_t *mCallbackOps;
         // Lock protecting the Camera object for modifications
         pthread_mutex_t mMutex;
+        // Array of handles to streams currently in use by the device
+        Stream **mStreams;
+        // Number of streams in mStreams
+        int mNumStreams;
 };
 } // namespace default_camera_hal
 
diff --git a/modules/camera/CameraHAL.cpp b/modules/camera/CameraHAL.cpp
index 6cae7d2..05b1fad 100644
--- a/modules/camera/CameraHAL.cpp
+++ b/modules/camera/CameraHAL.cpp
@@ -90,7 +90,6 @@
 {
     int id;
     char *nameEnd;
-    Camera *cam;
 
     ALOGV("%s: module=%p, name=%s, device=%p", __func__, mod, name, dev);
     id = strtol(name, &nameEnd, 10);
diff --git a/modules/camera/ScopedTrace.h b/modules/camera/ScopedTrace.h
new file mode 100644
index 0000000..ed00570
--- /dev/null
+++ b/modules/camera/ScopedTrace.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 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 CAMERA_SCOPED_TRACE_H
+#define CAMERA_SCOPED_TRACE_H
+
+#include <stdint.h>
+#include <cutils/trace.h>
+
+// See <cutils/trace.h> for more tracing macros.
+
+// CAMTRACE_NAME traces the beginning and end of the current scope.  To trace
+// the correct start and end times this macro should be declared first in the
+// scope body.
+#define CAMTRACE_NAME(name) ScopedTrace ___tracer(ATRACE_TAG, name)
+// CAMTRACE_CALL is an ATRACE_NAME that uses the current function name.
+#define CAMTRACE_CALL() CAMTRACE_NAME(__FUNCTION__)
+
+namespace default_camera_hal {
+
+class ScopedTrace {
+public:
+inline ScopedTrace(uint64_t tag, const char* name)
+    : mTag(tag) {
+    atrace_begin(mTag,name);
+}
+
+inline ~ScopedTrace() {
+    atrace_end(mTag);
+}
+
+private:
+    uint64_t mTag;
+};
+
+}; // namespace default_camera_hal
+
+#endif // CAMERA_SCOPED_TRACE_H
diff --git a/modules/camera/Stream.cpp b/modules/camera/Stream.cpp
new file mode 100644
index 0000000..0834aee
--- /dev/null
+++ b/modules/camera/Stream.cpp
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 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.
+ */
+
+#include <pthread.h>
+#include <hardware/camera3.h>
+#include <hardware/gralloc.h>
+#include <system/graphics.h>
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "Stream"
+#include <cutils/log.h>
+
+#define ATRACE_TAG (ATRACE_TAG_CAMERA | ATRACE_TAG_HAL)
+#include <cutils/trace.h>
+#include "ScopedTrace.h"
+
+#include "Stream.h"
+
+namespace default_camera_hal {
+
+Stream::Stream(int id, camera3_stream_t *s)
+  : mReuse(false),
+    mId(id),
+    mStream(s),
+    mType(s->stream_type),
+    mWidth(s->width),
+    mHeight(s->height),
+    mFormat(s->format),
+    mUsage(0),
+    mMaxBuffers(0),
+    mRegistered(false),
+    mBuffers(0),
+    mNumBuffers(0)
+{
+    // NULL (default) pthread mutex attributes
+    pthread_mutex_init(&mMutex, NULL);
+}
+
+Stream::~Stream()
+{
+    pthread_mutex_lock(&mMutex);
+    unregisterBuffers_L();
+    pthread_mutex_unlock(&mMutex);
+}
+
+void Stream::setUsage(uint32_t usage)
+{
+    pthread_mutex_lock(&mMutex);
+    if (usage != mUsage) {
+        mUsage = usage;
+        unregisterBuffers_L();
+    }
+    pthread_mutex_unlock(&mMutex);
+}
+
+void Stream::setMaxBuffers(uint32_t max_buffers)
+{
+    pthread_mutex_lock(&mMutex);
+    if (max_buffers != mMaxBuffers) {
+        mMaxBuffers = max_buffers;
+        unregisterBuffers_L();
+    }
+    pthread_mutex_unlock(&mMutex);
+}
+
+int Stream::getType()
+{
+    return mType;
+}
+
+bool Stream::isInputType()
+{
+    return mType & (CAMERA3_STREAM_INPUT | CAMERA3_STREAM_BIDIRECTIONAL);
+}
+
+bool Stream::isOutputType()
+{
+    return mType & (CAMERA3_STREAM_OUTPUT | CAMERA3_STREAM_BIDIRECTIONAL);
+}
+
+bool Stream::isRegistered()
+{
+    return mRegistered;
+}
+
+bool Stream::isValidReuseStream(int id, camera3_stream_t *s)
+{
+    if (id != mId) {
+        ALOGE("%s:%d: Invalid camera id for reuse. Got %d expect %d",
+                __func__, mId, id, mId);
+        return false;
+    }
+    if (s != mStream) {
+        ALOGE("%s:%d: Invalid stream handle for reuse. Got %p expect %p",
+                __func__, mId, s, mStream);
+        return false;
+    }
+    if (s->stream_type != mType) {
+        // TODO: prettyprint type string
+        ALOGE("%s:%d: Mismatched type in reused stream. Got %d expect %d",
+                __func__, mId, s->stream_type, mType);
+        return false;
+    }
+    if (s->format != mFormat) {
+        // TODO: prettyprint format string
+        ALOGE("%s:%d: Mismatched format in reused stream. Got %d expect %d",
+                __func__, mId, s->format, mFormat);
+        return false;
+    }
+    if (s->width != mWidth) {
+        ALOGE("%s:%d: Mismatched width in reused stream. Got %d expect %d",
+                __func__, mId, s->width, mWidth);
+        return false;
+    }
+    if (s->height != mHeight) {
+        ALOGE("%s:%d: Mismatched height in reused stream. Got %d expect %d",
+                __func__, mId, s->height, mHeight);
+        return false;
+    }
+    return true;
+}
+
+int Stream::registerBuffers(const camera3_stream_buffer_set_t *buf_set)
+{
+    CAMTRACE_CALL();
+
+    if (buf_set->stream != mStream) {
+        ALOGE("%s:%d: Buffer set for invalid stream. Got %p expect %p",
+                __func__, mId, buf_set->stream, mStream);
+        return -EINVAL;
+    }
+
+    pthread_mutex_lock(&mMutex);
+
+    mNumBuffers = buf_set->num_buffers;
+    mBuffers = new buffer_handle_t*[mNumBuffers];
+
+    for (unsigned int i = 0; i < mNumBuffers; i++) {
+        ALOGV("%s:%d: Registering buffer %p", __func__, mId,
+                buf_set->buffers[i]);
+        mBuffers[i] = buf_set->buffers[i];
+        // TODO: register buffers with hw, handle error cases
+    }
+    mRegistered = true;
+
+    pthread_mutex_unlock(&mMutex);
+
+    return 0;
+}
+
+// This must only be called with mMutex held
+void Stream::unregisterBuffers_L()
+{
+    mRegistered = false;
+    mNumBuffers = 0;
+    delete [] mBuffers;
+    // TODO: unregister buffers from hw
+}
+
+} // namespace default_camera_hal
diff --git a/modules/camera/Stream.h b/modules/camera/Stream.h
new file mode 100644
index 0000000..521362e
--- /dev/null
+++ b/modules/camera/Stream.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 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 STREAM_H_
+#define STREAM_H_
+
+#include <hardware/camera3.h>
+#include <hardware/gralloc.h>
+#include <system/graphics.h>
+
+namespace default_camera_hal {
+// Stream represents a single input or output stream for a camera device.
+class Stream {
+    public:
+        Stream(int id, camera3_stream_t *s);
+        ~Stream();
+
+        // validate that astream's parameters match this stream's parameters
+        bool isValidReuseStream(int id, camera3_stream_t *s);
+
+        // Register buffers with hardware
+        int registerBuffers(const camera3_stream_buffer_set_t *buf_set);
+
+        void setUsage(uint32_t usage);
+        void setMaxBuffers(uint32_t max_buffers);
+
+        int getType();
+        bool isInputType();
+        bool isOutputType();
+        bool isRegistered();
+
+        // This stream is being reused. Used in stream configuration passes
+        bool mReuse;
+
+    private:
+        // Clean up buffer state. must be called with mMutex held.
+        void unregisterBuffers_L();
+
+        // The camera device id this stream belongs to
+        const int mId;
+        // Handle to framework's stream, used as a cookie for buffers
+        const camera3_stream_t *mStream;
+        // Stream type: CAMERA3_STREAM_* (see <hardware/camera3.h>)
+        const int mType;
+        // Width in pixels of the buffers in this stream
+        const uint32_t mWidth;
+        // Height in pixels of the buffers in this stream
+        const uint32_t mHeight;
+        // Gralloc format: HAL_PIXEL_FORMAT_* (see <system/graphics.h>)
+        const int mFormat;
+        // Gralloc usage mask : GRALLOC_USAGE_* (see <hardware/gralloc.h>)
+        uint32_t mUsage;
+        // Max simultaneous in-flight buffers for this stream
+        uint32_t mMaxBuffers;
+        // Buffers have been registered for this stream and are ready
+        bool mRegistered;
+        // Array of handles to buffers currently in use by the stream
+        buffer_handle_t **mBuffers;
+        // Number of buffers in mBuffers
+        unsigned int mNumBuffers;
+        // Lock protecting the Stream object for modifications
+        pthread_mutex_t mMutex;
+};
+} // namespace default_camera_hal
+
+#endif // STREAM_H_
diff --git a/tests/hwc/Android.mk b/tests/hwc/Android.mk
new file mode 100644
index 0000000..4cae9eb
--- /dev/null
+++ b/tests/hwc/Android.mk
@@ -0,0 +1,14 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := libcnativewindow 
+LOCAL_SRC_FILES := cnativewindow.c util.c
+include $(BUILD_STATIC_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := hwc-test-arrows
+LOCAL_SRC_FILES := test-arrows.c
+LOCAL_STATIC_LIBRARIES := libcnativewindow
+LOCAL_SHARED_LIBRARIES := libEGL libGLESv2 libdl libhardware
+LOCAL_CFLAGS := -DGL_GLEXT_PROTOTYPES
+include $(BUILD_EXECUTABLE)
diff --git a/tests/hwc/cnativewindow.c b/tests/hwc/cnativewindow.c
new file mode 100644
index 0000000..474ceec
--- /dev/null
+++ b/tests/hwc/cnativewindow.c
@@ -0,0 +1,578 @@
+/*
+ * Copyright (C) 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.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+
+#include <pthread.h>
+
+#include <hardware/hardware.h>
+#include <hardware/gralloc.h>
+#include <hardware/hwcomposer.h>
+
+#include <system/window.h>
+#include <cutils/native_handle.h>
+
+// normalize and shorten type names
+typedef struct android_native_base_t aBase;
+typedef struct ANativeWindowBuffer aBuffer;
+typedef struct ANativeWindow aWindow;
+
+static int trace_level = 1;
+
+#define _TRACE(n,fmt...) \
+	do { if (trace_level >= n) fprintf(stderr, "CNW: " fmt); } while (0)
+
+#define ERROR(fmt...) _TRACE(0, fmt)
+#define INFO(fmt...) _TRACE(1, fmt)
+#define LOG(fmt...) _TRACE(2, fmt)
+#define TRACE(fmt...) _TRACE(3, fmt)
+
+#define QCT_WORKAROUND 1
+
+typedef struct CNativeBuffer {
+	aBuffer base;
+	struct CNativeBuffer *next;
+	struct CNativeBuffer *prev;
+	int ffd;
+} CNativeBuffer;
+
+typedef struct CNativeWindow {
+	aWindow base;
+
+	hwc_composer_device_1_t *hwc;
+	framebuffer_device_t *fb;
+	alloc_device_t *gr;
+
+	pthread_mutex_t lock;
+	pthread_cond_t cvar;
+
+	aBuffer *front;
+	aBuffer *spare;
+
+	CNativeBuffer free_buffer_queue;
+
+	unsigned width;
+	unsigned height;
+	unsigned xdpi;
+	unsigned ydpi;
+	unsigned format;
+
+	hwc_display_contents_1_t *dclist[HWC_NUM_DISPLAY_TYPES];
+
+	hwc_display_contents_1_t dc;
+	hwc_layer_1_t layer[4];
+} CNativeWindow;
+
+static inline CNativeBuffer *from_abuffer(aBuffer *buf) {
+	return (CNativeBuffer*) buf;
+}
+
+static CNativeBuffer *get_front(struct CNativeBuffer *queue) {
+	CNativeBuffer *buf = queue->next;
+	if (buf == queue)
+		return 0;
+	buf->next->prev = queue;
+	queue->next = buf->next;
+	buf->next = buf->prev = 0;
+	return buf;
+}
+
+static void put_front(struct CNativeBuffer *queue, aBuffer *_buf) {
+	struct CNativeBuffer *buf = (struct CNativeBuffer *) _buf;
+	buf->prev = queue;
+	buf->next = queue->next;
+	queue->next->prev = buf;
+	queue->next = buf;
+}
+
+static void put_back(struct CNativeBuffer *queue, aBuffer *_buf) {
+	struct CNativeBuffer *buf = (struct CNativeBuffer *) _buf;
+	buf->next = queue;
+	buf->prev = queue->prev;
+	queue->prev->next = buf;
+	queue->prev = buf;
+}
+
+static void cnw_inc_ref(aBase *base) { TRACE("buf %p ref++\n",base); }
+static void cnw_dec_ref(aBase *base) { TRACE("buf %p ref--\n",base); }
+
+static inline CNativeWindow *from_base(aWindow *base) {
+	return (CNativeWindow *) base;
+}
+
+static inline CNativeWindow *from_base_const(const aWindow *base) {
+	return (CNativeWindow *) base;
+}
+
+static int cnw_set_swap_interval(aWindow *base, int interval) {
+	CNativeWindow *win = from_base(base);
+	if (win->fb && win->fb->setSwapInterval)
+		return win->fb->setSwapInterval(win->fb, interval);
+	return 0;
+}
+
+static int cnw_dequeue_buffer1(aWindow *base, aBuffer **buf, int *ffd) {
+	CNativeWindow *win = from_base(base);
+	CNativeBuffer *cnb;
+
+	pthread_mutex_lock(&win->lock);
+
+	while ((cnb = get_front(&win->free_buffer_queue)) == 0) {
+		pthread_cond_wait(&win->cvar, &win->lock);
+	}
+
+	*ffd = cnb->ffd;
+	*buf = &cnb->base;
+	cnb->ffd = -1;
+	LOG("<< dequeue buffer %p %d\n", *buf, *ffd);
+
+	pthread_mutex_unlock(&win->lock);
+	return 0;
+}
+
+static int cnw_lock_buffer0(aWindow *base, aBuffer *buffer) {
+	return 0;
+}
+
+static void set_layer(hwc_layer_1_t *dl, aBuffer *buf, int ffd) {
+	int right = buf->width;
+	int bottom = buf->height;
+
+	dl->compositionType = HWC_FRAMEBUFFER;
+	dl->hints = 0;
+	dl->flags = 0;
+
+	dl->handle = buf->handle;
+	dl->transform = 0;
+	dl->blending = HWC_BLENDING_NONE;
+	dl->sourceCrop.left = 0;
+	dl->sourceCrop.top = 0;
+	dl->sourceCrop.right = right;
+	dl->sourceCrop.bottom = bottom;
+	dl->displayFrame.left = 0;
+	dl->displayFrame.top = 0;
+	dl->displayFrame.right = right;
+	dl->displayFrame.bottom = bottom;
+	dl->visibleRegionScreen.numRects = 1;
+	dl->visibleRegionScreen.rects = &dl->displayFrame;
+
+	dl->acquireFenceFd = ffd;
+	dl->releaseFenceFd = -1;
+}
+
+static void hwc_post(CNativeWindow *win, aBuffer *buf, int ffd) {
+	hwc_composer_device_1_t *hwc = win->hwc;
+	hwc_display_contents_1_t *dc = &(win->dc);
+	hwc_layer_1_t *dl = win->dc.hwLayers;
+	int r, i;
+
+	dc->retireFenceFd = -1;
+	dc->outbufAcquireFenceFd = -1;
+	dc->flags = HWC_GEOMETRY_CHANGED;
+	dc->numHwLayers = 1;
+
+	// some hwcomposers fail if these are NULL
+	dc->dpy = (void*) 0xdeadbeef;
+	dc->sur = (void*) 0xdeadbeef;
+
+	set_layer(&dl[0], buf, ffd);
+
+	if (QCT_WORKAROUND) {
+		set_layer(&dl[1], win->spare, -1);
+		dl[1].compositionType = HWC_FRAMEBUFFER_TARGET;
+		dc->numHwLayers++;
+	}
+
+	r = hwc->prepare(hwc, HWC_NUM_DISPLAY_TYPES, win->dclist);
+	if (r) {
+		ERROR("hwc->prepare failed r=%d\n",r);
+		return;
+	}
+
+//	for (i = 0; i < dc->numHwLayers; i++)
+//		LOG("dl[%d] ctype=0x%08x hints=0x%08x flags=0x%08x\n", i,
+//			dl[i].compositionType, dl[0].hints, dl[0].flags);
+
+	r = hwc->set(hwc, HWC_NUM_DISPLAY_TYPES, win->dclist);
+	if (r) {
+		ERROR("hwc->set failed, r=%d\n", r);
+		return;
+	}
+
+	if (dc->retireFenceFd != -1)
+		close(dc->retireFenceFd);
+	if (dl->releaseFenceFd != -1) {
+		CNativeBuffer *cnb = from_abuffer(buf);
+		cnb->ffd = dl->releaseFenceFd;
+	}
+	if (QCT_WORKAROUND)
+		if (dl[1].releaseFenceFd != -1)
+			close(dl[1].releaseFenceFd);
+}
+
+static int cnw_queue_buffer1(aWindow *base, aBuffer *buffer, int ffd) {
+	CNativeWindow *win = from_base(base);
+	int res;
+	LOG(">> queue buffer %p %d\n", buffer, ffd);
+	if (win->fb) {
+		res = win->fb->post(win->fb, buffer->handle);
+		if (ffd != -1)
+			close(ffd);
+	} else {
+		hwc_post(win, buffer, ffd);
+		res = 0;
+	}
+	pthread_mutex_lock(&win->lock);
+	if (win->front)
+		put_back(&win->free_buffer_queue, win->front);
+	win->front = buffer;
+	pthread_cond_signal(&win->cvar);
+	pthread_mutex_unlock(&win->lock);
+
+	return res;
+}
+
+static int cnw_cancel_buffer1(aWindow *base, aBuffer *buf, int ffd) {
+	CNativeWindow *win = from_base(base);
+	CNativeBuffer *cnb = from_abuffer(buf);
+	LOG("<< cancel buffer %p %d\n", buf, ffd);
+	cnb->ffd = ffd;
+	pthread_mutex_lock(&win->lock);
+	put_front(&win->free_buffer_queue, buf);
+	pthread_mutex_unlock(&win->lock);
+	return 0;
+}
+
+static int cnw_dequeue_buffer0(aWindow *base, aBuffer **buf) {
+	int ffd = -1;
+	int r;
+	r = cnw_dequeue_buffer1(base, buf, &ffd);
+	if (ffd != -1)
+		close(ffd);
+	return r;
+}
+
+static int cnw_queue_buffer0(aWindow *base, aBuffer *buf) {
+	return cnw_queue_buffer1(base, buf, -1);
+}
+
+static int cnw_cancel_buffer0(aWindow *base, aBuffer *buf) {
+	return cnw_cancel_buffer1(base, buf, -1);
+}
+
+static int cnw_query(const aWindow *base, int what, int *value) {
+	CNativeWindow *win = from_base_const(base);
+
+	switch (what) {
+	case NATIVE_WINDOW_WIDTH:
+	case NATIVE_WINDOW_DEFAULT_WIDTH:
+		*value = win->width;
+		TRACE("query window width: %d\n", *value);
+		return 0;
+	case NATIVE_WINDOW_HEIGHT:
+	case NATIVE_WINDOW_DEFAULT_HEIGHT:
+		*value = win->height;
+		TRACE("query window height: %d\n", *value);
+		return 0;
+	case NATIVE_WINDOW_FORMAT:
+		*value = win->format;
+		TRACE("query window format: %d\n", *value);
+		return 0;
+	case NATIVE_WINDOW_TRANSFORM_HINT:
+		TRACE("query transform hint: 0\n");
+		*value = 0;
+		return 0;
+	case NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS:
+		TRACE("query min undequeued buffers: 1\n");
+		*value = 1;
+		return 0;
+	default:
+		*value = 0;
+		ERROR("query %d unknown!\n", what);
+		return -EINVAL;
+	}
+}
+
+static int cnw_perform(aWindow *base, int op, ...) {
+	CNativeWindow *win = from_base(base);
+	va_list ap;
+	va_start(ap, op);
+
+	switch (op) {
+	case NATIVE_WINDOW_SET_USAGE:
+		TRACE("set usage %d\n", va_arg(ap,int));
+		return 0;
+	case NATIVE_WINDOW_CONNECT:
+	case NATIVE_WINDOW_DISCONNECT:
+	case NATIVE_WINDOW_API_CONNECT:
+	case NATIVE_WINDOW_API_DISCONNECT:
+		return 0;
+	case NATIVE_WINDOW_SET_BUFFERS_FORMAT:
+		TRACE("set buffers format %d\n", va_arg(ap,int));
+		return 0;
+	case NATIVE_WINDOW_SET_BUFFERS_TRANSFORM:
+		TRACE("set buffers transform %d\n", va_arg(ap,int));
+		return 0;
+	case NATIVE_WINDOW_SET_BUFFERS_TIMESTAMP:
+		TRACE("set buffers timestamp %lld\n", va_arg(ap,long long));
+		return 0;
+	case NATIVE_WINDOW_SET_SCALING_MODE:
+		TRACE("set scaling mode %d\n", va_arg(ap,int));
+		return 0;
+	case NATIVE_WINDOW_SET_BUFFERS_DIMENSIONS: {
+		int w = va_arg(ap,int);
+		int h = va_arg(ap,int);
+		if ((w == win->width) && (h == win->height)) {
+			TRACE("set buffers dimensions %d x %d\n", w, h);
+			return 0;
+		}
+		ERROR("cannot resize buffers to %d x %d\n", w, h);
+		return -1;
+	}
+	default:
+		ERROR("perform %d unknown!\n", op);
+		return -ENODEV;
+	}
+}
+
+static void hwc_invalidate(const struct hwc_procs *procs) {}
+static void hwc_vsync(const struct hwc_procs *procs, int disp, int64_t ts) {}
+static void hwc_hotplug(const struct hwc_procs *procs, int disp, int conn) {}
+
+struct hwc_procs hprocs = {
+	.invalidate = hwc_invalidate,
+	.vsync = hwc_vsync,
+	.hotplug = hwc_hotplug,
+};
+
+uint32_t attrs[] = {
+	HWC_DISPLAY_WIDTH,
+	HWC_DISPLAY_HEIGHT,
+	HWC_DISPLAY_VSYNC_PERIOD,
+	HWC_DISPLAY_DPI_X,
+	HWC_DISPLAY_DPI_Y,
+	HWC_DISPLAY_NO_ATTRIBUTE,
+};
+
+static int hwc_init(CNativeWindow *win) {
+	hw_module_t const* module;
+	hwc_composer_device_1_t *hwc;
+	unsigned i;
+	int r;
+	uint32_t configs[32];
+	uint32_t numconfigs = 32;
+	int32_t values[8];
+
+	if (hw_get_module(HWC_HARDWARE_MODULE_ID, &module) != 0) {
+		ERROR("cannot open hw composer module\n");
+		return -ENODEV;
+	}
+
+	if (hwc_open_1(module, &hwc)) {
+		ERROR("cannot open hwc device\n");
+		return -ENODEV;
+	}
+	win->hwc = hwc;
+
+	LOG("hwc version 0x%08x\n", hwc->common.version);
+
+	if ((hwc->common.version & 0xFFFF0000) < 0x01010000) {
+		ERROR("hwc version less than 1.1\n");
+		hwc_close_1(hwc);
+		return -ENODEV;
+	}
+
+	hwc->registerProcs(hwc, &hprocs);
+
+	if (hwc->getDisplayConfigs(hwc, 0, configs, &numconfigs)) {
+		ERROR("cannot get configs\n");
+		return -ENODEV;
+	}
+	for (i = 0; i < numconfigs; i++)
+		LOG("cfg[%d] = 0x%08x\n", i, configs[i]);
+
+	if ((r = hwc->getDisplayAttributes(hwc, 0, configs[0], attrs, values))) {
+		ERROR("cannot get attributes %d\n", r);
+		return -ENODEV;
+	}
+
+	win->width = values[0];
+	win->height = values[1];
+	win->xdpi = values[3];
+	win->ydpi = values[4];
+	win->format = HAL_PIXEL_FORMAT_RGBA_8888;
+
+	hwc->blank(hwc, 0, 0);
+
+	win->dclist[0] = &(win->dc);
+	return 0;
+}
+
+static aBuffer *cnw_alloc(CNativeWindow *win, unsigned format, unsigned usage) {
+	CNativeBuffer *cnb;
+	aBuffer *buf;
+	int err;
+
+	if (!(cnb = malloc(sizeof(CNativeBuffer))))
+		return 0;
+
+	buf = &cnb->base;
+	cnb->ffd = -1;
+
+	buf->common.magic = ANDROID_NATIVE_BUFFER_MAGIC;
+	buf->common.version = sizeof(aBuffer);
+	buf->common.incRef = cnw_inc_ref;
+	buf->common.decRef = cnw_dec_ref;
+
+	buf->width = win->width;
+	buf->height = win->height;
+	buf->format = format;
+	buf->usage = usage;
+
+	err = win->gr->alloc(win->gr, win->width, win->height,
+		format, usage, &buf->handle, &buf->stride);
+	if (err) {
+		ERROR("gralloc of %d x %d failed: err=%d\n",
+			win->width, win->height, err);
+		free(buf);
+		return 0;
+	}
+	INFO("alloc buffer %p %d x %d\n", buf, win->width, win->height);
+	return buf;
+}
+
+static int cnw_init(CNativeWindow *win) {
+	hw_module_t const* module;
+	framebuffer_device_t *fb = NULL;
+	alloc_device_t *gr;
+	int err, i, n;
+	unsigned usage, format;
+
+	memset(win, 0, sizeof(CNativeWindow));
+
+	win->free_buffer_queue.next = &(win->free_buffer_queue);
+	win->free_buffer_queue.prev = &(win->free_buffer_queue);
+
+	if (hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &module) != 0) {
+		ERROR("cannot open gralloc module\n");
+		return -ENODEV;
+	}
+
+	if (hwc_init(win)) {
+		ERROR("cannot open hwcomposer, trying legacy fb HAL\n");
+		err = framebuffer_open(module, &fb);
+		if (err) {
+			ERROR("cannot open fb HAL (%s)", strerror(-err));
+			return -ENODEV;
+		}
+		win->width = fb->width;
+		win->height = fb->height;
+		win->format = fb->format;
+		win->xdpi = fb->xdpi;
+		win->ydpi = fb->ydpi;
+		win->fb = fb;
+	}
+
+	INFO("display %d x %d fmt=%d\n",
+		win->width, win->height, win->format);
+
+	err = gralloc_open(module, &gr);
+	if (err) {
+		ERROR("couldn't open gralloc HAL (%s)", strerror(-err));
+		return -ENODEV;
+	}
+	win->gr = gr;
+
+	usage = GRALLOC_USAGE_HW_FB |
+		GRALLOC_USAGE_HW_COMPOSER |
+		GRALLOC_USAGE_HW_RENDER;
+
+	for (i = 0; i < 2; i++) {
+		aBuffer *buf = cnw_alloc(win, win->format, usage);
+		if (!buf)
+			return -ENOMEM;
+		put_back(&win->free_buffer_queue, buf);
+	}
+
+	if (!win->fb && QCT_WORKAROUND) {
+		win->spare = cnw_alloc(win, win->format, usage);
+		if (!win->spare)
+			return -ENOMEM;
+	}
+
+	// Disgusting, but we need to init these "const" fields
+	// and unlike C++ we can't use const_cast<>
+	*((float*) &win->base.xdpi) = win->xdpi;
+	*((float*) &win->base.ydpi) = win->ydpi;
+	*((int*) &win->base.minSwapInterval) = 1;
+	*((int*) &win->base.maxSwapInterval) = 1;
+
+	win->base.common.magic = ANDROID_NATIVE_WINDOW_MAGIC;
+	win->base.common.version = sizeof(aWindow);
+	win->base.common.incRef = cnw_inc_ref;
+	win->base.common.decRef = cnw_dec_ref;
+
+	win->base.setSwapInterval = cnw_set_swap_interval;
+	win->base.dequeueBuffer_DEPRECATED = cnw_dequeue_buffer0;
+	win->base.lockBuffer_DEPRECATED = cnw_lock_buffer0;
+	win->base.queueBuffer_DEPRECATED = cnw_queue_buffer0;
+	win->base.query = cnw_query;
+	win->base.perform = cnw_perform;
+	win->base.cancelBuffer_DEPRECATED = cnw_cancel_buffer0;
+	win->base.dequeueBuffer = cnw_dequeue_buffer1;
+	win->base.queueBuffer = cnw_queue_buffer1;
+	win->base.cancelBuffer = cnw_cancel_buffer1;
+
+	pthread_mutex_init(&win->lock, NULL);
+	pthread_cond_init(&win->cvar, NULL);
+
+	return 0;
+}
+
+void cnw_destroy(CNativeWindow *win) {
+	if (win->fb)
+		framebuffer_close(win->fb);
+	if (win->hwc)
+		hwc_close_1(win->hwc);
+	if (win->gr)
+		gralloc_close(win->gr);
+	free(win);
+}
+
+CNativeWindow *cnw_create(void) {
+	CNativeWindow *win;
+	char *x;
+	if ((x = getenv("CNWDEBUG")))
+		trace_level = atoi(x);
+	if (!(win = malloc(sizeof(CNativeWindow))))
+		return NULL;
+	if (cnw_init(win)) {
+		cnw_destroy(win);
+		return NULL;
+	}
+	return win;
+}
+
+void cnw_info(CNativeWindow *win, unsigned *w, unsigned *h, unsigned *fmt) {
+	*w = win->width;
+	*h = win->height;
+	*fmt = win->format;
+}
+
diff --git a/tests/hwc/test-arrows.c b/tests/hwc/test-arrows.c
new file mode 100644
index 0000000..a35faa7
--- /dev/null
+++ b/tests/hwc/test-arrows.c
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 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.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <EGL/egl.h>
+#include <GLES2/gl2.h>
+
+#include "util.h"
+
+static const char gVertexShader[] =
+	"attribute vec4 aPosition;\n"
+	"uniform mat4 uTransform;\n"
+	"varying vec4 vTexCoord;\n"
+	"void main() {\n"
+	"  gl_Position = aPosition * uTransform;\n"
+	"  vTexCoord = aPosition * vec4(1.0/16.0,-1.0/16.0,0.0,0.0);\n"
+	"}\n";
+
+static const char gFragmentShader[] =
+	"precision mediump float;\n"
+	"uniform sampler2D uTexture;\n"
+	"uniform float uAnim;\n"
+	"varying vec4 vTexCoord;\n"
+	"void main() {\n"
+	"  vec2 tc = vec2(vTexCoord.x, uAnim + vTexCoord.y);\n"
+	"  gl_FragColor = texture2D(uTexture, tc);\n"
+	"}\n";
+
+static GLuint pgm;
+static GLint aPosition, uTransform, uTexture, uAnim;
+
+static GLfloat vtx[2 * 3 * 2];
+static GLfloat mtx[16];
+
+//#define R (0xFF0000FF)
+#define R (0xFF000000)
+#define G (0xFF00FF00)
+uint32_t t32[] = {
+	R, R, R, R, R, R, R, G, G, R, R, R, R, R, R, R,
+	R, R, R, R, R, R, G, G, G, G, R, R, R, R, R, R,
+	R, R, R, R, R, G, G, G, G, G, G, R, R, R, R, R,
+	R, R, R, R, G, G, G, G, G, G, G, G, R, R, R, R,
+	R, R, R, G, G, G, G, G, G, G, G, G, G, R, R, R,
+	R, R, G, G, G, G, G, G, G, G, G, G, G, G, R, R,
+	R, R, G, G, G, G, G, G, G, G, G, G, G, G, R, R,
+	R, R, R, R, R, R, G, G, G, G, R, R, R, R, R, R,
+	R, R, R, R, R, R, G, G, G, G, R, R, R, R, R, R,
+	R, R, R, R, R, R, G, G, G, G, R, R, R, R, R, R,
+	R, R, R, R, R, R, G, G, G, G, R, R, R, R, R, R,
+	R, R, R, R, R, R, G, G, G, G, R, R, R, R, R, R,
+	R, R, R, R, R, R, G, G, G, G, R, R, R, R, R, R,
+	R, R, R, R, R, R, R, R, R, R, R, R, R, R, R, R,
+	R, R, R, R, R, R, R, R, R, R, R, R, R, R, R, R,
+	R, R, R, R, R, R, R, R, R, R, R, R, R, R, R, R,
+};
+#undef R
+#undef G
+
+int prepare(int w, int h) {
+	GLuint texid;
+
+	int left = w / 4;
+	int top = h / 4;
+	int right = (w / 4) * 3;
+	int bottom = (h / 4) * 3;
+
+	vtx[0] = left;
+	vtx[1] = top;
+	vtx[2] = left;
+	vtx[3] = bottom;
+	vtx[4] = right;
+	vtx[5] = bottom;
+
+	vtx[6] = right;
+	vtx[7] = bottom;
+	vtx[8] = right;
+	vtx[9] = top;
+	vtx[10] = left;
+	vtx[11] = top;
+
+	matrix_init_ortho(mtx, w, h);
+
+	pgm = load_program(gVertexShader, gFragmentShader);
+	if (!pgm)
+		return -1;
+
+	aPosition = glGetAttribLocation(pgm, "aPosition");
+	uTexture = glGetUniformLocation(pgm, "uTexture");
+	uTransform = glGetUniformLocation(pgm, "uTransform");
+	uAnim = glGetUniformLocation(pgm, "uAnim");
+
+	glViewport(0, 0, w, h);
+
+	glGenTextures(1, &texid);
+	glActiveTexture(GL_TEXTURE0);
+	glBindTexture(GL_TEXTURE_2D, texid);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+	glEnable(GL_TEXTURE_2D);
+	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0,
+		GL_RGBA, GL_UNSIGNED_BYTE, t32);
+
+	return 0;
+}
+
+static float anim = 0.0;
+
+void render() {
+	anim += 0.1;
+	if (anim >= 16.0) anim = 0.0;
+
+	glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+	glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
+	glUseProgram(pgm);
+	glUniform1i(uTexture, 0);
+	glUniform1f(uAnim, anim);
+	glUniformMatrix4fv(uTransform, 1, 0, mtx);
+	glVertexAttribPointer(aPosition, 2, GL_FLOAT, GL_FALSE, 0, vtx);
+	glEnableVertexAttribArray(aPosition);
+	glDrawArrays(GL_TRIANGLES, 0, 6);
+}
+
+int main(int argc, char **argv) {
+	EGLDisplay display;
+	EGLSurface surface;
+	int w, h, count;
+
+	if (argc > 1)
+		count = atoi(argv[1]);
+
+	if (egl_create(&display, &surface, &w, &h))
+		return -1;
+
+	if (prepare(w, h))
+		return -1;
+
+	for (;;) {
+		render();
+		eglSwapBuffers(display, surface);
+		if (count > 0)
+			if (--count == 0)
+				break;
+	}
+
+	egl_destroy(display, surface);
+	return 0;
+}
diff --git a/tests/hwc/util.c b/tests/hwc/util.c
new file mode 100644
index 0000000..8931305
--- /dev/null
+++ b/tests/hwc/util.c
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 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.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+
+#include <EGL/egl.h>
+#include <GLES2/gl2.h>
+
+#include <system/graphics.h>
+
+#include "util.h"
+
+void matrix_init_ortho(GLfloat *m, float w, float h) {
+	m[0] = 2.0 / w;
+	m[1] = 0.0;
+	m[2] = 0.0;
+	m[3] = -1.0;
+	m[4] = 0.0;
+	m[5] = 2.0 / h;
+	m[6] = 0.0;
+	m[7] = -1.0;
+	m[8] = 0.0;
+	m[9] = 0.0;
+	m[10] -1.0;
+	m[11] = 0.0;
+	m[12] = 0.0;
+	m[13] = 0.0;
+	m[14] = 0.0;
+	m[15] = 1.0;
+}
+
+static GLuint load_shader(GLenum shaderType, const char *src) {
+	GLint status = 0, len = 0;
+	GLuint shader;
+
+	if (!(shader = glCreateShader(shaderType)))
+		return 0;
+
+	glShaderSource(shader, 1, &src, NULL);
+	glCompileShader(shader);
+	glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
+
+	if (status)
+		return shader;
+
+	glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &len);
+	if (len) {
+		char *msg = malloc(len);
+		if (msg) {
+			glGetShaderInfoLog(shader, len, NULL, msg);
+			msg[len-1] = 0;
+			fprintf(stderr, "error compiling shader:\n%s\n", msg);
+			free(msg);
+		}
+	}
+	glDeleteShader(shader);
+	return 0;
+}
+
+GLuint load_program(const char *vert_src, const char *frag_src) {
+	GLuint vert, frag, prog;
+	GLint status = 0, len = 0;
+
+	if (!(vert = load_shader(GL_VERTEX_SHADER, vert_src)))
+		return 0;
+	if (!(frag = load_shader(GL_FRAGMENT_SHADER, frag_src)))
+		goto fail_frag;
+	if (!(prog = glCreateProgram()))
+		goto fail_prog;
+
+	glAttachShader(prog, vert);
+	glAttachShader(prog, frag);
+	glLinkProgram(prog);
+
+	glGetProgramiv(prog, GL_LINK_STATUS, &status);
+	if (status)
+		return prog;
+
+	glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &len);
+	if (len) {
+		char *buf = (char*) malloc(len);
+		if (buf) {
+			glGetProgramInfoLog(prog, len, NULL, buf);
+			buf[len-1] = 0;
+			fprintf(stderr, "error linking program:\n%s\n", buf);
+			free(buf);
+		}
+	}
+	glDeleteProgram(prog);
+fail_prog:
+	glDeleteShader(frag);
+fail_frag:
+	glDeleteShader(vert);
+	return 0;
+}
+
+int select_config_for_window(EGLDisplay dpy, EGLint *attr,
+	unsigned format, EGLConfig *config) {
+	EGLint R,G,B,A,r,g,b,a;
+	EGLint i, n, max;
+	EGLConfig *cfg;
+
+	switch (format) {
+	case HAL_PIXEL_FORMAT_RGBA_8888:
+	case HAL_PIXEL_FORMAT_BGRA_8888:
+		R = G = B = A = 8;
+		break;
+	case HAL_PIXEL_FORMAT_RGB_565:
+		R = 5; G = 6; B = 5; A = 0;
+		break;
+	default:
+		fprintf(stderr, "unknown fb pixel format %d\n", format);
+		return -1;
+	}
+
+	if (eglGetConfigs(dpy, NULL, 0, &max) == EGL_FALSE) {
+		fprintf(stderr, "no EGL configurations available?!\n");
+		return -1;
+	}
+
+	cfg = (EGLConfig*) malloc(sizeof(EGLConfig) * max);
+	if (!cfg)
+		return -1;
+
+	if (eglChooseConfig(dpy, attr, cfg, max, &n) == EGL_FALSE) {
+		fprintf(stderr, "eglChooseConfig failed\n");
+		return -1;
+	}
+
+	for (i = 0; i < n; i++) {
+		EGLint r,g,b,a;
+		eglGetConfigAttrib(dpy, cfg[i], EGL_RED_SIZE,   &r);
+		eglGetConfigAttrib(dpy, cfg[i], EGL_GREEN_SIZE, &g);
+		eglGetConfigAttrib(dpy, cfg[i], EGL_BLUE_SIZE,  &b);
+		eglGetConfigAttrib(dpy, cfg[i], EGL_ALPHA_SIZE, &a);
+		if (r == R && g == G && b == B && a == A) {
+			*config = cfg[i];
+			free(cfg);
+			return 0;
+		}
+	}
+
+	fprintf(stderr, "cannot find matching config\n");
+	free(cfg);
+	return -1;
+}
+
+static struct CNativeWindow *_cnw = 0;
+
+int egl_create(EGLDisplay *_display, EGLSurface *_surface, int *_w, int *_h) {
+	EGLBoolean res;
+	EGLConfig config = { 0 };
+	EGLint context_attrs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
+	EGLint config_attrs[] = {
+		EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
+		EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+		EGL_NONE };
+	EGLint major, minor;
+	EGLContext context;
+	EGLSurface surface;
+	EGLint w, h;
+	EGLDisplay display;
+	EGLNativeWindowType window;
+	unsigned width, height, format;
+	struct CNativeWindow *cnw;
+
+	display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+	if (display == EGL_NO_DISPLAY)
+		return -1;
+
+	if (!(res = eglInitialize(display, &major, &minor)))
+		return -1;
+
+	fprintf(stderr, "egl version: %d.%d\n", major, minor);
+
+	if ((cnw = cnw_create()) == 0)
+		return -1;
+
+	cnw_info(cnw, &width, &height, &format);
+	window = (EGLNativeWindowType) cnw;
+
+	if ((res = select_config_for_window(display, config_attrs, format, &config)))
+		goto fail;
+
+	surface = eglCreateWindowSurface(display, config, window, NULL);
+	if (surface == EGL_NO_SURFACE)
+		goto fail;
+
+	context = eglCreateContext(display, config, EGL_NO_CONTEXT, context_attrs);
+	if (context == EGL_NO_CONTEXT)
+		goto fail;
+
+	if (!(res = eglMakeCurrent(display, surface, surface, context)))
+		goto fail;
+
+	eglQuerySurface(display, surface, EGL_WIDTH, &w);
+	eglQuerySurface(display, surface, EGL_HEIGHT, &h);
+
+	fprintf(stderr, "window: %d x %d\n", w, h);
+
+	*_display = display;
+	*_surface = surface;
+	*_w = w;
+	*_h = h;
+
+	_cnw = cnw;
+	return 0;
+
+fail:
+	cnw_destroy(cnw);
+	return -1;
+}
+
+void egl_destroy(EGLDisplay display, EGLSurface surface) {
+	if (_cnw) {
+		eglDestroySurface(display, surface);
+		eglTerminate(display);
+		cnw_destroy(_cnw);
+		_cnw = 0;
+	}
+}
diff --git a/tests/hwc/util.h b/tests/hwc/util.h
new file mode 100644
index 0000000..a0d38ce
--- /dev/null
+++ b/tests/hwc/util.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 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 _GL_UTIL_H_
+#define _GL_UTIL_H_
+
+/* convenience */
+
+GLuint load_program(const char *vert_src, const char *frag_src);
+void matrix_init_ortho(GLfloat *m, float w, float h);
+
+/* context setup / teardown */
+
+int egl_create(EGLDisplay *_display, EGLSurface *_surface, int *_w, int *_h);
+void egl_destroy(EGLDisplay display, EGLSurface surface);
+
+/* internals needed by util.c */
+
+struct CNativeWindow;
+struct CNativeWindow *cnw_create(void);
+void cnw_destroy(struct CNativeWindow *win);
+void cnw_info(struct CNativeWindow *win,
+	unsigned *w, unsigned *h, unsigned *fmt);
+
+#endif