am 11348f99: Fix threading and stopping issues in Colors dream.

* commit '11348f99ee2ffaeb3f705b669ae886beda97656f':
  Fix threading and stopping issues in Colors dream.
diff --git a/src/com/android/dreams/basic/Colors.java b/src/com/android/dreams/basic/Colors.java
index 021189f..5f16d2c 100644
--- a/src/com/android/dreams/basic/Colors.java
+++ b/src/com/android/dreams/basic/Colors.java
@@ -16,411 +16,36 @@
 
 package com.android.dreams.basic;
 
-import android.graphics.Color;
 import android.graphics.SurfaceTexture;
-import android.service.dreams.Dream;
+import android.service.dreams.DreamService;
 import android.util.Log;
-import android.view.Choreographer;
-import android.view.Choreographer.FrameCallback;
 import android.view.TextureView;
-import android.os.Looper;
-import android.os.SystemClock;
+import android.os.Handler;
+import android.os.HandlerThread;
 
-import javax.microedition.khronos.egl.EGL10;
-import javax.microedition.khronos.egl.EGLConfig;
-import javax.microedition.khronos.egl.EGLContext;
-import javax.microedition.khronos.egl.EGLDisplay;
-import javax.microedition.khronos.egl.EGLSurface;
-import javax.microedition.khronos.opengles.GL;
-import android.opengl.GLUtils;
-
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.FloatBuffer;
-import java.nio.ShortBuffer;
-
-import static android.opengl.GLES20.*;
-
-public class Colors extends Dream implements TextureView.SurfaceTextureListener {
+/**
+ * Plays a delightful show of colors.
+ * <p>
+ * This dream performs its rendering using OpenGL on a separate rendering thread.
+ * </p>
+ */
+public class Colors extends DreamService implements TextureView.SurfaceTextureListener {
     static final String TAG = Colors.class.getSimpleName();
-    static final boolean DEBUG = true;
+    static final boolean DEBUG = false;
+
     public static final void LOG(String fmt, Object... args) {
         if (!DEBUG) return;
         Log.v(TAG, String.format(fmt, args));
     }
 
-    // It's so easy to use OpenGLES 2.0!
-    private EGL10 mEgl;
-    private EGLDisplay mEglDisplay;
-    private EGLConfig mEglConfig;
-    private EGLContext mEglContext;
-    private EGLSurface mEglSurface;
-    private SurfaceTexture mSurface;
-    private GL mGL;
+    private TextureView mTextureView;
 
-    static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
-    static final int EGL_OPENGL_ES2_BIT = 4;
+    // The handler thread and handler on which the GL renderer is running.
+    private HandlerThread mRendererHandlerThread;
+    private Handler mRendererHandler;
 
-    volatile boolean mStop = false;
-
-    Square mSquare;
-    TextureView mTextureView;
-
-    private long mLastFrameTime;
-    private int mFrameNum = 0;
-
-    private FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
-        @Override
-        public void doFrame(long frameTimeNanos) {
-            if (mStop) {
-                Looper.myLooper().quit();
-                return;
-            }
-
-            if (mSquare == null) {
-                initGL();
-
-                mSquare = new Square();
-                glClearColor(1f, 0f, 0f, 1.0f);
-
-                if (DEBUG) {
-                    mLastFrameTime = frameTimeNanos;
-                }
-            }
-
-            checkCurrent();
-
-            glViewport(0, 0, mWidth, mHeight);
-
-            if (DEBUG) {
-                mFrameNum ++;
-                final long t2 = frameTimeNanos;
-                final long dt = t2-mLastFrameTime;
-                final int fps = (int) (1e9f/dt);
-                if (0 == (mFrameNum % 10)) {
-                    LOG("frame %d fps=%d", mFrameNum, fps);
-                }
-                if (fps < 40) {
-                    LOG("JANK! (%d ms)", dt);
-                }
-                mLastFrameTime = t2;
-            }
-
-            glClear(GL_COLOR_BUFFER_BIT);
-            checkGlError();
-
-            mSquare.draw();
-
-            if (!mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) {
-                throw new RuntimeException("Cannot swap buffers");
-            }
-            checkEglError();
-
-            mChoreographer.postFrameCallback(mFrameCallback);
-        }
-    };
-
-    private int mHeight;
-
-    private int mWidth;
-    private Choreographer mChoreographer;
-
-    class Square {
-        // Straight from the API guide
-        private final String vertexShaderCode =
-            "attribute vec4 a_position;" +
-            "attribute vec4 a_color;" +
-            "varying vec4 v_color;" +
-            "void main() {" +
-            "  gl_Position = a_position;" +
-            "  v_color = a_color;" +
-            "}";
-
-        private final String fragmentShaderCode =
-            "precision mediump float;" +
-            "varying vec4 v_color;" +
-            "void main() {" +
-            "  gl_FragColor = v_color;" +
-            "}";
-
-        private final FloatBuffer vertexBuffer;
-        private final FloatBuffer colorBuffer;
-        private final int mProgram;
-        private int mPositionHandle;
-        private int mColorHandle;
-
-        private ShortBuffer drawListBuffer;
-
-
-        // number of coordinates per vertex in this array
-        final int COORDS_PER_VERTEX = 3;
-        float squareCoords[] = { -1f,  1f, 0f,   // top left
-                                 -1f, -1f, 0f,   // bottom left
-                                  1f, -1f, 0f,   // bottom right
-                                  1f,  1f, 0f }; // top right
-
-        private short drawOrder[] = { 0, 1, 2, 0, 2, 3 }; // order to draw vertices (CCW)
-
-        private final float HUES[] = { // reverse order due to CCW winding
-                60,  // yellow
-                120, // green
-                343, // red
-                200, // blue
-        };
-
-        private final int vertexCount = squareCoords.length / COORDS_PER_VERTEX;
-        private final int vertexStride = COORDS_PER_VERTEX * 4; // bytes per vertex
-
-        private float cornerFrequencies[] = new float[vertexCount];
-        private int cornerRotation;
-
-        final int COLOR_PLANES_PER_VERTEX = 4;
-        private final int colorStride = COLOR_PLANES_PER_VERTEX * 4; // bytes per vertex
-
-        // Set color with red, green, blue and alpha (opacity) values
-        float color[] = { 0.63671875f, 0.76953125f, 0.22265625f, 1.0f };
-
-        public Square() {
-            for (int i=0; i<vertexCount; i++) {
-                cornerFrequencies[i] = 1f + (float)(Math.random() * 5);
-            }
-            cornerRotation = (int)(Math.random() * vertexCount);
-            // initialize vertex byte buffer for shape coordinates
-            ByteBuffer bb = ByteBuffer.allocateDirect(
-            // (# of coordinate values * 4 bytes per float)
-                    squareCoords.length * 4);
-            bb.order(ByteOrder.nativeOrder());
-            vertexBuffer = bb.asFloatBuffer();
-            vertexBuffer.put(squareCoords);
-            vertexBuffer.position(0);
-
-            bb = ByteBuffer.allocateDirect(vertexCount * colorStride);
-            bb.order(ByteOrder.nativeOrder());
-            colorBuffer = bb.asFloatBuffer();
-
-            // initialize byte buffer for the draw list
-            ByteBuffer dlb = ByteBuffer.allocateDirect(
-            // (# of coordinate values * 2 bytes per short)
-                    drawOrder.length * 2);
-            dlb.order(ByteOrder.nativeOrder());
-            drawListBuffer = dlb.asShortBuffer();
-            drawListBuffer.put(drawOrder);
-            drawListBuffer.position(0);
-
-            mProgram = buildProgram(vertexShaderCode, fragmentShaderCode);
-
-            // Add program to OpenGL environment
-            glUseProgram(mProgram);
-            checkGlError("glUseProgram(" + mProgram + ")");
-
-            // get handle to vertex shader's a_position member
-            mPositionHandle = glGetAttribLocation(mProgram, "a_position");
-            checkGlError("glGetAttribLocation(a_position)");
-
-            // Enable a handle to the triangle vertices
-            glEnableVertexAttribArray(mPositionHandle);
-
-            // Prepare the triangle coordinate data
-            glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,
-                    GL_FLOAT, false,
-                    vertexStride, vertexBuffer);
-
-            mColorHandle = glGetAttribLocation(mProgram, "a_color");
-            checkGlError("glGetAttribLocation(a_color)");
-            glEnableVertexAttribArray(mColorHandle);
-            checkGlError("glEnableVertexAttribArray");
-        }
-
-        final float[] _tmphsv = new float[3];
-        public void draw() {
-            // same thing for colors
-            long now = SystemClock.uptimeMillis();
-            colorBuffer.clear();
-            final float t = now / 4000f; // set the base period to 4sec
-            for(int i=0; i<vertexCount; i++) {
-                final float freq = (float) Math.sin(2 * Math.PI * t / cornerFrequencies[i]);
-                _tmphsv[0] = HUES[(i + cornerRotation) % vertexCount];
-                _tmphsv[1] = 1f;
-                _tmphsv[2] = freq * 0.25f + 0.75f;
-                final int c = Color.HSVToColor(_tmphsv);
-                colorBuffer.put((float)((c & 0xFF0000) >> 16) / 0xFF);
-                colorBuffer.put((float)((c & 0x00FF00) >> 8) / 0xFF);
-                colorBuffer.put((float)(c & 0x0000FF) / 0xFF);
-                colorBuffer.put(/*a*/ 1f);
-            }
-            colorBuffer.position(0);
-            glVertexAttribPointer(mColorHandle, COLOR_PLANES_PER_VERTEX,
-                    GL_FLOAT, false,
-                    colorStride, colorBuffer);
-            checkGlError("glVertexAttribPointer");
-
-            // Draw the triangle
-            glDrawArrays(GL_TRIANGLE_FAN, 0, vertexCount);
-        }
-    }
-
-    private static int buildProgram(String vertex, String fragment) {
-        int vertexShader = buildShader(vertex, GL_VERTEX_SHADER);
-        if (vertexShader == 0) return 0;
-
-        int fragmentShader = buildShader(fragment, GL_FRAGMENT_SHADER);
-        if (fragmentShader == 0) return 0;
-
-        int program = glCreateProgram();
-        glAttachShader(program, vertexShader);
-        checkGlError();
-
-        glAttachShader(program, fragmentShader);
-        checkGlError();
-
-        glLinkProgram(program);
-        checkGlError();
-
-        int[] status = new int[1];
-        glGetProgramiv(program, GL_LINK_STATUS, status, 0);
-        if (status[0] != GL_TRUE) {
-            String error = glGetProgramInfoLog(program);
-            Log.d(TAG, "Error while linking program:\n" + error);
-            glDeleteShader(vertexShader);
-            glDeleteShader(fragmentShader);
-            glDeleteProgram(program);
-            return 0;
-        }
-
-        return program;
-    }
-
-    private static int buildShader(String source, int type) {
-        int shader = glCreateShader(type);
-
-        glShaderSource(shader, source);
-        checkGlError();
-
-        glCompileShader(shader);
-        checkGlError();
-
-        int[] status = new int[1];
-        glGetShaderiv(shader, GL_COMPILE_STATUS, status, 0);
-        if (status[0] != GL_TRUE) {
-            String error = glGetShaderInfoLog(shader);
-            Log.d(TAG, "Error while compiling shader:\n" + error);
-            glDeleteShader(shader);
-            return 0;
-        }
-
-        return shader;
-    }
-    @Override
-    public void onStart() {
-        super.onStart();
-    }
-
-    private void checkEglError() {
-        int error = mEgl.eglGetError();
-        if (error != EGL10.EGL_SUCCESS) {
-            Log.w(TAG, "EGL error = 0x" + Integer.toHexString(error));
-        }
-    }
-
-    private static void checkGlError() {
-        checkGlError("");
-    }
-
-    private static void checkGlError(String what) {
-        int error = glGetError();
-        if (error != GL_NO_ERROR) {
-            Log.w(TAG, "GL error: (" + what + ") = 0x" + Integer.toHexString(error));
-        }
-    }
-
-    private void finishGL() {
-        mEgl.eglDestroyContext(mEglDisplay, mEglContext);
-        mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
-    }
-
-    private void checkCurrent() {
-        if (!mEglContext.equals(mEgl.eglGetCurrentContext()) ||
-                !mEglSurface.equals(mEgl.eglGetCurrentSurface(EGL10.EGL_DRAW))) {
-            if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
-                throw new RuntimeException("eglMakeCurrent failed "
-                        + GLUtils.getEGLErrorString(mEgl.eglGetError()));
-            }
-        }
-    }
-
-    private void initGL() {
-        mEgl = (EGL10) EGLContext.getEGL();
-
-        mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
-        if (mEglDisplay == EGL10.EGL_NO_DISPLAY) {
-            throw new RuntimeException("eglGetDisplay failed "
-                    + GLUtils.getEGLErrorString(mEgl.eglGetError()));
-        }
-
-        int[] version = new int[2];
-        if (!mEgl.eglInitialize(mEglDisplay, version)) {
-            throw new RuntimeException("eglInitialize failed " +
-                    GLUtils.getEGLErrorString(mEgl.eglGetError()));
-        }
-
-        mEglConfig = chooseEglConfig();
-        if (mEglConfig == null) {
-            throw new RuntimeException("eglConfig not initialized");
-        }
-
-        mEglContext = createContext(mEgl, mEglDisplay, mEglConfig);
-
-        mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig, mSurface, null);
-
-        if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {
-            int error = mEgl.eglGetError();
-            if (error == EGL10.EGL_BAD_NATIVE_WINDOW) {
-                Log.e(TAG, "createWindowSurface returned EGL_BAD_NATIVE_WINDOW.");
-                return;
-            }
-            throw new RuntimeException("createWindowSurface failed "
-                    + GLUtils.getEGLErrorString(error));
-        }
-
-        if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
-            throw new RuntimeException("eglMakeCurrent failed "
-                    + GLUtils.getEGLErrorString(mEgl.eglGetError()));
-        }
-
-        mGL = mEglContext.getGL();
-    }
-
-
-    EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) {
-        int[] attrib_list = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE };
-        return egl.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list);
-    }
-
-    private EGLConfig chooseEglConfig() {
-        int[] configsCount = new int[1];
-        EGLConfig[] configs = new EGLConfig[1];
-        int[] configSpec = getConfig();
-        if (!mEgl.eglChooseConfig(mEglDisplay, configSpec, configs, 1, configsCount)) {
-            throw new IllegalArgumentException("eglChooseConfig failed " +
-                    GLUtils.getEGLErrorString(mEgl.eglGetError()));
-        } else if (configsCount[0] > 0) {
-            return configs[0];
-        }
-        return null;
-    }
-
-    private static int[] getConfig() {
-        return new int[] {
-                EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
-                EGL10.EGL_RED_SIZE, 8,
-                EGL10.EGL_GREEN_SIZE, 8,
-                EGL10.EGL_BLUE_SIZE, 8,
-                EGL10.EGL_ALPHA_SIZE, 8,
-                EGL10.EGL_DEPTH_SIZE, 0,
-                EGL10.EGL_STENCIL_SIZE, 0,
-                EGL10.EGL_NONE
-        };
-    }
+    // The current GL renderer, or null if the dream is not running.
+    private ColorsGLRenderer mRenderer;
 
     @Override
     public void onCreate() {
@@ -430,6 +55,12 @@
 
         mTextureView = new TextureView(this);
         mTextureView.setSurfaceTextureListener(this);
+
+        if (mRendererHandlerThread == null) {
+            mRendererHandlerThread = new HandlerThread(TAG);
+            mRendererHandlerThread.start();
+            mRendererHandler = new Handler(mRendererHandlerThread.getLooper());
+        }
     }
 
     @Override
@@ -441,36 +72,50 @@
     }
 
     @Override
-    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
+    public void onSurfaceTextureAvailable(final SurfaceTexture surface,
+            final int width, final int height) {
         LOG("onSurfaceTextureAvailable(%s, %d, %d)", surface, width, height);
-        mSurface = surface;
 
-        mWidth = width;
-        mHeight = height;
-
-        new Thread() {
+        mRendererHandler.post(new Runnable() {
             @Override
             public void run() {
-                Looper.prepare();
-    
-                mChoreographer = Choreographer.getInstance();
-                mChoreographer.postFrameCallback(mFrameCallback);
-
-                Looper.loop();
+                if (mRenderer != null) {
+                    mRenderer.stop();
+                }
+                mRenderer = new ColorsGLRenderer(surface, width, height);
+                mRenderer.start();
             }
-        }.start();
+        });
     }
 
     @Override
-    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
+    public void onSurfaceTextureSizeChanged(SurfaceTexture surface,
+            final int width, final int height) {
         LOG("onSurfaceTextureSizeChanged(%s, %d, %d)", surface, width, height);
-        mWidth = width;
-        mHeight = height;
+
+        mRendererHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                if (mRenderer != null) {
+                    mRenderer.setSize(width, height);
+                }
+            }
+        });
     }
 
     @Override
     public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
         LOG("onSurfaceTextureDestroyed(%s)", surface);
+
+        mRendererHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                if (mRenderer != null) {
+                    mRenderer.stop();
+                    mRenderer = null;
+                }
+            }
+        });
         return false;
     }
 
@@ -478,11 +123,4 @@
     public void onSurfaceTextureUpdated(SurfaceTexture surface) {
         LOG("onSurfaceTextureUpdated(%s)", surface);
     }
-
-    @Override
-    public void finish() {
-        mStop = true;
-        finishGL();
-        super.finish();
-    }
 }
diff --git a/src/com/android/dreams/basic/ColorsGLRenderer.java b/src/com/android/dreams/basic/ColorsGLRenderer.java
new file mode 100644
index 0000000..3452261
--- /dev/null
+++ b/src/com/android/dreams/basic/ColorsGLRenderer.java
@@ -0,0 +1,439 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+package com.android.dreams.basic;
+
+import android.graphics.Color;
+import android.graphics.SurfaceTexture;
+import android.util.Log;
+import android.view.Choreographer;
+import android.os.SystemClock;
+
+import javax.microedition.khronos.egl.EGL10;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.egl.EGLContext;
+import javax.microedition.khronos.egl.EGLDisplay;
+import javax.microedition.khronos.egl.EGLSurface;
+import javax.microedition.khronos.opengles.GL;
+
+import android.opengl.EGL14;
+import android.opengl.GLUtils;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+
+import static android.opengl.GLES20.*;
+
+/**
+ * The OpenGL renderer for the {@link Colors} dream.
+ * <p>
+ * This class is single-threaded.  Its methods must only be called on the
+ * rendering thread.
+ * </p>
+ */
+final class ColorsGLRenderer implements Choreographer.FrameCallback {
+    static final String TAG = ColorsGLRenderer.class.getSimpleName();
+    static final boolean DEBUG = false;
+
+    private static final void LOG(String fmt, Object... args) {
+        if (!DEBUG) return;
+        Log.v(TAG, String.format(fmt, args));
+    }
+
+    private final SurfaceTexture mSurface;
+    private int mWidth;
+    private int mHeight;
+
+    private final Choreographer mChoreographer;
+
+    private Square mSquare;
+    private long mLastFrameTime;
+    private int mFrameNum = 0;
+
+    // It's so easy to use OpenGLES 2.0!
+    private EGL10 mEgl;
+    private EGLDisplay mEglDisplay;
+    private EGLConfig mEglConfig;
+    private EGLContext mEglContext;
+    private EGLSurface mEglSurface;
+    private GL mGL;
+
+    public ColorsGLRenderer(SurfaceTexture surface, int width, int height) {
+        mSurface = surface;
+        mWidth = width;
+        mHeight = height;
+
+        mChoreographer = Choreographer.getInstance();
+    }
+
+    public void start() {
+        initGL();
+        mSquare = new Square();
+
+        mFrameNum = 0;
+        mChoreographer.postFrameCallback(this);
+    }
+
+    public void stop() {
+        mChoreographer.removeFrameCallback(this);
+
+        mSquare = null;
+        finishGL();
+    }
+
+    public void setSize(int width, int height) {
+        mWidth = width;
+        mHeight = height;
+    }
+
+    @Override
+    public void doFrame(long frameTimeNanos) {
+        mFrameNum += 1;
+
+        // Clear on first frame.
+        if (mFrameNum == 1) {
+            glClearColor(1f, 0f, 0f, 1.0f);
+            if (DEBUG) {
+                mLastFrameTime = frameTimeNanos;
+            }
+        }
+
+        // Draw new frame.
+        checkCurrent();
+
+        glViewport(0, 0, mWidth, mHeight);
+
+        if (DEBUG) {
+            final long t2 = frameTimeNanos;
+            final long dt = t2 - mLastFrameTime;
+            final int fps = (int) (1e9f / dt);
+            if (0 == (mFrameNum % 10)) {
+                LOG("frame %d fps=%d", mFrameNum, fps);
+            }
+            if (fps < 40) {
+                LOG("JANK! (%d ms)", dt);
+            }
+            mLastFrameTime = t2;
+        }
+
+        glClear(GL_COLOR_BUFFER_BIT);
+        checkGlError();
+
+        mSquare.draw();
+
+        if (!mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) {
+            throw new RuntimeException("Cannot swap buffers");
+        }
+        checkEglError();
+
+        // Animate.  Post callback to run on next vsync.
+        mChoreographer.postFrameCallback(this);
+    }
+
+    private void checkCurrent() {
+        if (!mEglContext.equals(mEgl.eglGetCurrentContext()) ||
+                !mEglSurface.equals(mEgl.eglGetCurrentSurface(EGL10.EGL_DRAW))) {
+            if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
+                throw new RuntimeException("eglMakeCurrent failed "
+                        + GLUtils.getEGLErrorString(mEgl.eglGetError()));
+            }
+        }
+    }
+
+    private void initGL() {
+        mEgl = (EGL10) EGLContext.getEGL();
+
+        mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
+        if (mEglDisplay == EGL10.EGL_NO_DISPLAY) {
+            throw new RuntimeException("eglGetDisplay failed "
+                    + GLUtils.getEGLErrorString(mEgl.eglGetError()));
+        }
+
+        int[] version = new int[2];
+        if (!mEgl.eglInitialize(mEglDisplay, version)) {
+            throw new RuntimeException("eglInitialize failed " +
+                    GLUtils.getEGLErrorString(mEgl.eglGetError()));
+        }
+
+        mEglConfig = chooseEglConfig();
+        if (mEglConfig == null) {
+            throw new RuntimeException("eglConfig not initialized");
+        }
+
+        mEglContext = createContext(mEgl, mEglDisplay, mEglConfig);
+
+        mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig, mSurface, null);
+
+        if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {
+            int error = mEgl.eglGetError();
+            if (error == EGL10.EGL_BAD_NATIVE_WINDOW) {
+                Log.e(TAG, "createWindowSurface returned EGL_BAD_NATIVE_WINDOW.");
+                return;
+            }
+            throw new RuntimeException("createWindowSurface failed "
+                    + GLUtils.getEGLErrorString(error));
+        }
+
+        if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
+            throw new RuntimeException("eglMakeCurrent failed "
+                    + GLUtils.getEGLErrorString(mEgl.eglGetError()));
+        }
+
+        mGL = mEglContext.getGL();
+    }
+
+    private void finishGL() {
+        mEgl.eglDestroyContext(mEglDisplay, mEglContext);
+        mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
+    }
+
+    private EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) {
+        int[] attrib_list = { EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE };
+        return egl.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list);
+    }
+
+    private EGLConfig chooseEglConfig() {
+        int[] configsCount = new int[1];
+        EGLConfig[] configs = new EGLConfig[1];
+        int[] configSpec = getConfig();
+        if (!mEgl.eglChooseConfig(mEglDisplay, configSpec, configs, 1, configsCount)) {
+            throw new IllegalArgumentException("eglChooseConfig failed " +
+                    GLUtils.getEGLErrorString(mEgl.eglGetError()));
+        } else if (configsCount[0] > 0) {
+            return configs[0];
+        }
+        return null;
+    }
+
+    private static int[] getConfig() {
+        return new int[] {
+                EGL10.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
+                EGL10.EGL_RED_SIZE, 8,
+                EGL10.EGL_GREEN_SIZE, 8,
+                EGL10.EGL_BLUE_SIZE, 8,
+                EGL10.EGL_ALPHA_SIZE, 8,
+                EGL10.EGL_DEPTH_SIZE, 0,
+                EGL10.EGL_STENCIL_SIZE, 0,
+                EGL10.EGL_NONE
+        };
+    }
+
+    private static int buildProgram(String vertex, String fragment) {
+        int vertexShader = buildShader(vertex, GL_VERTEX_SHADER);
+        if (vertexShader == 0) return 0;
+
+        int fragmentShader = buildShader(fragment, GL_FRAGMENT_SHADER);
+        if (fragmentShader == 0) return 0;
+
+        int program = glCreateProgram();
+        glAttachShader(program, vertexShader);
+        checkGlError();
+
+        glAttachShader(program, fragmentShader);
+        checkGlError();
+
+        glLinkProgram(program);
+        checkGlError();
+
+        int[] status = new int[1];
+        glGetProgramiv(program, GL_LINK_STATUS, status, 0);
+        if (status[0] != GL_TRUE) {
+            String error = glGetProgramInfoLog(program);
+            Log.d(TAG, "Error while linking program:\n" + error);
+            glDeleteShader(vertexShader);
+            glDeleteShader(fragmentShader);
+            glDeleteProgram(program);
+            return 0;
+        }
+
+        return program;
+    }
+
+    private static int buildShader(String source, int type) {
+        int shader = glCreateShader(type);
+
+        glShaderSource(shader, source);
+        checkGlError();
+
+        glCompileShader(shader);
+        checkGlError();
+
+        int[] status = new int[1];
+        glGetShaderiv(shader, GL_COMPILE_STATUS, status, 0);
+        if (status[0] != GL_TRUE) {
+            String error = glGetShaderInfoLog(shader);
+            Log.d(TAG, "Error while compiling shader:\n" + error);
+            glDeleteShader(shader);
+            return 0;
+        }
+
+        return shader;
+    }
+
+    private void checkEglError() {
+        int error = mEgl.eglGetError();
+        if (error != EGL10.EGL_SUCCESS) {
+            Log.w(TAG, "EGL error = 0x" + Integer.toHexString(error));
+        }
+    }
+
+    private static void checkGlError() {
+        checkGlError("");
+    }
+
+    private static void checkGlError(String what) {
+        int error = glGetError();
+        if (error != GL_NO_ERROR) {
+            Log.w(TAG, "GL error: (" + what + ") = 0x" + Integer.toHexString(error));
+        }
+    }
+
+    private final static class Square {
+        // Straight from the API guide
+        private final String vertexShaderCode =
+            "attribute vec4 a_position;" +
+            "attribute vec4 a_color;" +
+            "varying vec4 v_color;" +
+            "void main() {" +
+            "  gl_Position = a_position;" +
+            "  v_color = a_color;" +
+            "}";
+
+        private final String fragmentShaderCode =
+            "precision mediump float;" +
+            "varying vec4 v_color;" +
+            "void main() {" +
+            "  gl_FragColor = v_color;" +
+            "}";
+
+        private final FloatBuffer vertexBuffer;
+        private final FloatBuffer colorBuffer;
+        private final int mProgram;
+        private int mPositionHandle;
+        private int mColorHandle;
+
+        private ShortBuffer drawListBuffer;
+
+
+        // number of coordinates per vertex in this array
+        final int COORDS_PER_VERTEX = 3;
+        float squareCoords[] = { -1f,  1f, 0f,   // top left
+                                 -1f, -1f, 0f,   // bottom left
+                                  1f, -1f, 0f,   // bottom right
+                                  1f,  1f, 0f }; // top right
+
+        private short drawOrder[] = { 0, 1, 2, 0, 2, 3 }; // order to draw vertices (CCW)
+
+        private final float HUES[] = { // reverse order due to CCW winding
+                60,  // yellow
+                120, // green
+                343, // red
+                200, // blue
+        };
+
+        private final int vertexCount = squareCoords.length / COORDS_PER_VERTEX;
+        private final int vertexStride = COORDS_PER_VERTEX * 4; // bytes per vertex
+
+        private float cornerFrequencies[] = new float[vertexCount];
+        private int cornerRotation;
+
+        final int COLOR_PLANES_PER_VERTEX = 4;
+        private final int colorStride = COLOR_PLANES_PER_VERTEX * 4; // bytes per vertex
+
+        // Set color with red, green, blue and alpha (opacity) values
+        float color[] = { 0.63671875f, 0.76953125f, 0.22265625f, 1.0f };
+
+        public Square() {
+            for (int i=0; i<vertexCount; i++) {
+                cornerFrequencies[i] = 1f + (float)(Math.random() * 5);
+            }
+            cornerRotation = (int)(Math.random() * vertexCount);
+            // initialize vertex byte buffer for shape coordinates
+            ByteBuffer bb = ByteBuffer.allocateDirect(
+            // (# of coordinate values * 4 bytes per float)
+                    squareCoords.length * 4);
+            bb.order(ByteOrder.nativeOrder());
+            vertexBuffer = bb.asFloatBuffer();
+            vertexBuffer.put(squareCoords);
+            vertexBuffer.position(0);
+
+            bb = ByteBuffer.allocateDirect(vertexCount * colorStride);
+            bb.order(ByteOrder.nativeOrder());
+            colorBuffer = bb.asFloatBuffer();
+
+            // initialize byte buffer for the draw list
+            ByteBuffer dlb = ByteBuffer.allocateDirect(
+            // (# of coordinate values * 2 bytes per short)
+                    drawOrder.length * 2);
+            dlb.order(ByteOrder.nativeOrder());
+            drawListBuffer = dlb.asShortBuffer();
+            drawListBuffer.put(drawOrder);
+            drawListBuffer.position(0);
+
+            mProgram = buildProgram(vertexShaderCode, fragmentShaderCode);
+
+            // Add program to OpenGL environment
+            glUseProgram(mProgram);
+            checkGlError("glUseProgram(" + mProgram + ")");
+
+            // get handle to vertex shader's a_position member
+            mPositionHandle = glGetAttribLocation(mProgram, "a_position");
+            checkGlError("glGetAttribLocation(a_position)");
+
+            // Enable a handle to the triangle vertices
+            glEnableVertexAttribArray(mPositionHandle);
+
+            // Prepare the triangle coordinate data
+            glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,
+                    GL_FLOAT, false,
+                    vertexStride, vertexBuffer);
+
+            mColorHandle = glGetAttribLocation(mProgram, "a_color");
+            checkGlError("glGetAttribLocation(a_color)");
+            glEnableVertexAttribArray(mColorHandle);
+            checkGlError("glEnableVertexAttribArray");
+        }
+
+        final float[] _tmphsv = new float[3];
+        public void draw() {
+            // same thing for colors
+            long now = SystemClock.uptimeMillis();
+            colorBuffer.clear();
+            final float t = now / 4000f; // set the base period to 4sec
+            for(int i=0; i<vertexCount; i++) {
+                final float freq = (float) Math.sin(2 * Math.PI * t / cornerFrequencies[i]);
+                _tmphsv[0] = HUES[(i + cornerRotation) % vertexCount];
+                _tmphsv[1] = 1f;
+                _tmphsv[2] = freq * 0.25f + 0.75f;
+                final int c = Color.HSVToColor(_tmphsv);
+                colorBuffer.put((float)((c & 0xFF0000) >> 16) / 0xFF);
+                colorBuffer.put((float)((c & 0x00FF00) >> 8) / 0xFF);
+                colorBuffer.put((float)(c & 0x0000FF) / 0xFF);
+                colorBuffer.put(/*a*/ 1f);
+            }
+            colorBuffer.position(0);
+            glVertexAttribPointer(mColorHandle, COLOR_PLANES_PER_VERTEX,
+                    GL_FLOAT, false,
+                    colorStride, colorBuffer);
+            checkGlError("glVertexAttribPointer");
+
+            // Draw the triangle
+            glDrawArrays(GL_TRIANGLE_FAN, 0, vertexCount);
+        }
+    }
+}