Merge "Fix uploading multiple files via MtpDocumentsProvider."
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index 5b88c8e..16f825d 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -408,15 +408,18 @@
 
     /**
      * Returns true if any of the child animations of this AnimatorSet have been started and have
-     * not yet ended.
-     * @return Whether this AnimatorSet has been started and has not yet ended.
+     * not yet ended. Child animations will not be started until the AnimatorSet has gone past
+     * its initial delay set through {@link #setStartDelay(long)}.
+     *
+     * @return Whether this AnimatorSet has gone past the initial delay, and at least one child
+     *         animation has been started and not yet ended.
      */
     @Override
     public boolean isRunning() {
         int size = mNodes.size();
         for (int i = 0; i < size; i++) {
             Node node = mNodes.get(i);
-            if (node != mRootNode && node.mAnimation.isRunning()) {
+            if (node != mRootNode && node.mAnimation.isStarted()) {
                 return true;
             }
         }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index c37b4f9..e841f85 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -393,6 +393,7 @@
                             Slog.e(TAG, "Invalid root tag: " + tag);
                             return;
                         }
+                        continue;
                     }
                     // readInner() will only see START_TAG at depth >= 2.
                     if (!readInner(parser, depth, tag)) {
diff --git a/tests/UiBench/Android.mk b/tests/UiBench/Android.mk
index 1e5b117..24df85b 100644
--- a/tests/UiBench/Android.mk
+++ b/tests/UiBench/Android.mk
@@ -10,14 +10,18 @@
 # regressions are reflected in test data
 LOCAL_RESOURCE_DIR := \
     $(LOCAL_PATH)/res \
-    frameworks/support/v7/appcompat/res
+    frameworks/support/v7/appcompat/res \
+    frameworks/support/v7/cardview/res
 
 LOCAL_AAPT_FLAGS := \
-    --extra-packages android.support.v7.appcompat
+    --auto-add-overlay \
+    --extra-packages android.support.v7.appcompat \
+    --extra-packages android.support.v7.cardview
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     android-support-v4 \
-    android-support-v7-appcompat
+    android-support-v7-appcompat \
+    android-support-v7-cardview
 
 LOCAL_PACKAGE_NAME := UiBench
 
diff --git a/tests/UiBench/AndroidManifest.xml b/tests/UiBench/AndroidManifest.xml
index 6677e0d..d9df9f8 100644
--- a/tests/UiBench/AndroidManifest.xml
+++ b/tests/UiBench/AndroidManifest.xml
@@ -32,10 +32,10 @@
             </intent-filter>
         </activity>
 
-        <!-- Tests -->
+        <!-- General -->
         <activity
             android:name=".GlTextureViewActivity"
-            android:label="Microbenchmarks/GL TextureView" >
+            android:label="General/GL TextureView" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="com.android.test.uibench.TEST" />
@@ -43,7 +43,15 @@
         </activity>
         <activity
             android:name=".FullscreenOverdrawActivity"
-            android:label="Microbenchmarks/Fullscreen Overdraw" >
+            android:label="General/Fullscreen Overdraw" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="com.android.test.uibench.TEST" />
+            </intent-filter>
+        </activity>
+        <activity
+            android:name=".InvalidateActivity"
+            android:label="General/Invalidate" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="com.android.test.uibench.TEST" />
@@ -51,7 +59,7 @@
         </activity>
         <activity
             android:name=".TrivialAnimationActivity"
-            android:label="Microbenchmarks/Trivial Animation" >
+            android:label="General/Trivial Animation" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="com.android.test.uibench.TEST" />
@@ -59,7 +67,27 @@
         </activity>
         <activity
             android:name=".TrivialListActivity"
-            android:label="Microbenchmarks/Trivial ListView" >
+            android:label="General/Trivial ListView" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="com.android.test.uibench.TEST" />
+            </intent-filter>
+        </activity>
+
+        <!-- GPU -->
+        <activity
+            android:name=".BitmapUploadActivity"
+            android:label="GPU/Bitmap Upload" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="com.android.test.uibench.TEST" />
+            </intent-filter>
+        </activity>
+
+        <!-- Text -->
+        <activity
+            android:name=".EditTextTypeActivity"
+            android:label="Text/EditText Typing" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="com.android.test.uibench.TEST" />
diff --git a/tests/UiBench/build.gradle b/tests/UiBench/build.gradle
index bf0ed11..deecbb6 100644
--- a/tests/UiBench/build.gradle
+++ b/tests/UiBench/build.gradle
@@ -34,4 +34,5 @@
     // Dependencies enumerated specifically for platform-independent / reproducible builds.
     compile 'com.android.support:support-v4:23.0.0'
     compile 'com.android.support:appcompat-v7:23.0.0'
+    compile 'com.android.support:cardview-v7:23.0.0'
 }
diff --git a/tests/UiBench/res/layout/activity_bitmap_upload.xml b/tests/UiBench/res/layout/activity_bitmap_upload.xml
new file mode 100644
index 0000000..70faa07
--- /dev/null
+++ b/tests/UiBench/res/layout/activity_bitmap_upload.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 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
+  -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/upload_root"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:padding="10dp"
+    android:clipToPadding="false">
+    <android.support.v7.widget.CardView
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1">
+        <view class="com.android.test.uibench.BitmapUploadActivity$UploadView"
+            android:id="@+id/upload_view"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"/>
+    </android.support.v7.widget.CardView>
+
+    <android.support.v4.widget.Space
+        android:layout_height="10dp"
+        android:layout_width="match_parent" />
+
+    <android.support.v7.widget.CardView
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1" />
+
+    <android.support.v4.widget.Space
+        android:layout_height="10dp"
+        android:layout_width="match_parent" />
+
+    <android.support.v7.widget.CardView
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1" />
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/UiBench/res/layout/activity_invalidate.xml b/tests/UiBench/res/layout/activity_invalidate.xml
new file mode 100644
index 0000000..34bcca9
--- /dev/null
+++ b/tests/UiBench/res/layout/activity_invalidate.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 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
+  -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/invalidate_root"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <include layout="@layout/invalidate_row"/>
+    <include layout="@layout/invalidate_row"/>
+    <include layout="@layout/invalidate_row"/>
+    <include layout="@layout/invalidate_row"/>
+    <include layout="@layout/invalidate_row"/>
+
+    <include layout="@layout/invalidate_row"/>
+    <include layout="@layout/invalidate_row"/>
+    <include layout="@layout/invalidate_row"/>
+    <include layout="@layout/invalidate_row"/>
+    <include layout="@layout/invalidate_row"/>
+
+    <include layout="@layout/invalidate_row"/>
+    <include layout="@layout/invalidate_row"/>
+    <include layout="@layout/invalidate_row"/>
+    <include layout="@layout/invalidate_row"/>
+    <include layout="@layout/invalidate_row"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/UiBench/res/layout/invalidate_row.xml b/tests/UiBench/res/layout/invalidate_row.xml
new file mode 100644
index 0000000..9feefde
--- /dev/null
+++ b/tests/UiBench/res/layout/invalidate_row.xml
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 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
+  -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="horizontal"
+    android:layout_width="match_parent"
+    android:layout_height="0dp"
+    android:layout_weight="1">
+    <view class="com.android.test.uibench.InvalidateActivity$ColorView"
+        android:layout_weight="1"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"/>
+    <view class="com.android.test.uibench.InvalidateActivity$ColorView"
+        android:layout_weight="1"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"/>
+    <view class="com.android.test.uibench.InvalidateActivity$ColorView"
+        android:layout_weight="1"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"/>
+    <view class="com.android.test.uibench.InvalidateActivity$ColorView"
+        android:layout_weight="1"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"/>
+    <view class="com.android.test.uibench.InvalidateActivity$ColorView"
+        android:layout_weight="1"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"/>
+
+    <view class="com.android.test.uibench.InvalidateActivity$ColorView"
+        android:layout_weight="1"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"/>
+    <view class="com.android.test.uibench.InvalidateActivity$ColorView"
+        android:layout_weight="1"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"/>
+    <view class="com.android.test.uibench.InvalidateActivity$ColorView"
+        android:layout_weight="1"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"/>
+    <view class="com.android.test.uibench.InvalidateActivity$ColorView"
+        android:layout_weight="1"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"/>
+    <view class="com.android.test.uibench.InvalidateActivity$ColorView"
+        android:layout_weight="1"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"/>
+
+    <view class="com.android.test.uibench.InvalidateActivity$ColorView"
+        android:layout_weight="1"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"/>
+    <view class="com.android.test.uibench.InvalidateActivity$ColorView"
+        android:layout_weight="1"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"/>
+    <view class="com.android.test.uibench.InvalidateActivity$ColorView"
+        android:layout_weight="1"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"/>
+    <view class="com.android.test.uibench.InvalidateActivity$ColorView"
+        android:layout_weight="1"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"/>
+    <view class="com.android.test.uibench.InvalidateActivity$ColorView"
+        android:layout_weight="1"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/UiBench/src/com/android/test/uibench/BitmapUploadActivity.java b/tests/UiBench/src/com/android/test/uibench/BitmapUploadActivity.java
new file mode 100644
index 0000000..e2bf897
--- /dev/null
+++ b/tests/UiBench/src/com/android/test/uibench/BitmapUploadActivity.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2015 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.test.uibench;
+
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.view.View;
+
+public class BitmapUploadActivity extends AppCompatActivity {
+    public static class UploadView extends View {
+        private int mColorValue;
+        private Bitmap mBitmap;
+        private final DisplayMetrics mMetrics = new DisplayMetrics();
+        private final Rect mRect = new Rect();
+
+        public UploadView(Context context, AttributeSet attrs) {
+            super(context, attrs);
+        }
+
+        @SuppressWarnings("unused")
+        public void setColorValue(int colorValue) {
+            if (colorValue == mColorValue) return;
+
+            mColorValue = colorValue;
+
+            // modify the bitmap's color to ensure it's uploaded to the GPU
+            mBitmap.eraseColor(Color.rgb(mColorValue, 255 - mColorValue, 255));
+
+            invalidate();
+        }
+
+        @Override
+        protected void onAttachedToWindow() {
+            super.onAttachedToWindow();
+
+            getDisplay().getMetrics(mMetrics);
+            int minDisplayDimen = Math.min(mMetrics.widthPixels, mMetrics.heightPixels);
+            int bitmapSize = Math.min((int) (minDisplayDimen * 0.75), 720);
+            if (mBitmap == null
+                    || mBitmap.getWidth() != bitmapSize
+                    || mBitmap.getHeight() != bitmapSize) {
+                mBitmap = Bitmap.createBitmap(bitmapSize, bitmapSize, Bitmap.Config.ARGB_8888);
+            }
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            if (mBitmap != null) {
+                mRect.set(0, 0, getWidth(), getHeight());
+                canvas.drawBitmap(mBitmap, null, mRect, null);
+            }
+        }
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_bitmap_upload);
+
+        // animate color to force bitmap uploads
+        UploadView uploadView = (UploadView) findViewById(R.id.upload_view);
+        ObjectAnimator colorValueAnimator = ObjectAnimator.ofInt(uploadView, "colorValue", 0, 255);
+        colorValueAnimator.setRepeatMode(ValueAnimator.REVERSE);
+        colorValueAnimator.setRepeatCount(ValueAnimator.INFINITE);
+        colorValueAnimator.start();
+
+        // animate scene root to guarantee there's a minimum amount of GPU rendering work
+        View uploadRoot = findViewById(R.id.upload_root);
+        ObjectAnimator yAnimator = ObjectAnimator.ofFloat(uploadRoot, "translationY", 0, 100);
+        yAnimator.setRepeatMode(ValueAnimator.REVERSE);
+        yAnimator.setRepeatCount(ValueAnimator.INFINITE);
+        yAnimator.start();
+    }
+}
diff --git a/tests/UiBench/src/com/android/test/uibench/EditTextTypeActivity.java b/tests/UiBench/src/com/android/test/uibench/EditTextTypeActivity.java
new file mode 100644
index 0000000..08ab510
--- /dev/null
+++ b/tests/UiBench/src/com/android/test/uibench/EditTextTypeActivity.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2015 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.test.uibench;
+
+import android.app.Instrumentation;
+import android.os.Bundle;
+import android.os.Looper;
+import android.os.MessageQueue;
+import android.support.v7.app.AppCompatActivity;
+import android.view.KeyEvent;
+import android.widget.EditText;
+
+import java.util.concurrent.Semaphore;
+
+/**
+ * Note: currently incomplete, complexity of input continuously grows, instead of looping
+ * over a stable amount of work.
+ *
+ * Simulates typing continuously into an EditText.
+ */
+public class EditTextTypeActivity extends AppCompatActivity {
+    Thread mThread;
+
+    private static String sSeedText = "";
+    static {
+        final int count = 100;
+        final String string = "hello ";
+
+        StringBuilder builder = new StringBuilder(count * string.length());
+        for (int i = 0; i < count; i++) {
+            builder.append(string);
+        }
+        sSeedText = builder.toString();
+    }
+
+    final Object mLock = new Object();
+    boolean mShouldStop = false;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        EditText editText = new EditText(this);
+        editText.setText(sSeedText);
+        setContentView(editText);
+
+        final Instrumentation instrumentation = new Instrumentation();
+        final Semaphore sem = new Semaphore(0);
+        MessageQueue.IdleHandler handler = new MessageQueue.IdleHandler() {
+            @Override
+            public boolean queueIdle() {
+                // TODO: consider other signaling approaches
+                sem.release();
+                return true;
+            }
+        };
+        Looper.myQueue().addIdleHandler(handler);
+        synchronized (mLock) {
+            mShouldStop = false;
+        }
+        mThread = new Thread(new Runnable() {
+            int codes[] = { KeyEvent.KEYCODE_H, KeyEvent.KEYCODE_E, KeyEvent.KEYCODE_L,
+                    KeyEvent.KEYCODE_L, KeyEvent.KEYCODE_O, KeyEvent.KEYCODE_SPACE };
+            int i = 0;
+            @Override
+            public void run() {
+                while (true) {
+                    try {
+                        sem.acquire();
+                    } catch (InterruptedException e) {
+                        // TODO, maybe
+                    }
+                    int code = codes[i % codes.length];
+                    if (i % 100 == 99) code = KeyEvent.KEYCODE_ENTER;
+
+                    synchronized (mLock) {
+                        if (mShouldStop) break;
+                    }
+
+                    // TODO: bit of a race here, since the event can arrive after pause/stop.
+                    // (Can't synchronize on key send, since it's synchronous.)
+                    instrumentation.sendKeyDownUpSync(code);
+                    i++;
+                }
+            }
+        });
+        mThread.start();
+    }
+
+    @Override
+    protected void onPause() {
+        synchronized (mLock) {
+            mShouldStop = true;
+        }
+        super.onPause();
+    }
+}
diff --git a/tests/UiBench/src/com/android/test/uibench/GlTextureViewActivity.java b/tests/UiBench/src/com/android/test/uibench/GlTextureViewActivity.java
index ce79259..a12742d 100644
--- a/tests/UiBench/src/com/android/test/uibench/GlTextureViewActivity.java
+++ b/tests/UiBench/src/com/android/test/uibench/GlTextureViewActivity.java
@@ -18,39 +18,19 @@
 
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
 import android.graphics.SurfaceTexture;
-import android.opengl.GLUtils;
 import android.os.Bundle;
-import android.os.Environment;
 import android.support.v7.app.AppCompatActivity;
 import android.util.Log;
 import android.view.Gravity;
 import android.view.TextureView;
-import android.view.View;
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
 
-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 java.io.BufferedOutputStream;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.FloatBuffer;
-
-import static android.opengl.GLES20.*;
+import com.android.test.uibench.opengl.ImageFlipRenderThread;
 
 public class GlTextureViewActivity extends AppCompatActivity implements TextureView.SurfaceTextureListener {
-    private RenderThread mRenderThread;
+    private ImageFlipRenderThread mRenderThread;
     private TextureView mTextureView;
 
     @Override
@@ -66,7 +46,7 @@
 
     @Override
     public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
-        mRenderThread = new RenderThread(getResources(), surface);
+        mRenderThread = new ImageFlipRenderThread(getResources(), surface);
         mRenderThread.start();
 
         mTextureView.setCameraDistance(5000);
@@ -94,7 +74,7 @@
         try {
             mRenderThread.join();
         } catch (InterruptedException e) {
-            Log.e(RenderThread.LOG_TAG, "Could not wait for render thread");
+            Log.e(ImageFlipRenderThread.LOG_TAG, "Could not wait for render thread");
         }
         return true;
     }
@@ -103,311 +83,4 @@
     public void onSurfaceTextureUpdated(SurfaceTexture surface) {
     }
 
-    private static class RenderThread extends Thread {
-        private static final String LOG_TAG = "GLTextureView";
-
-        static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
-        static final int EGL_OPENGL_ES2_BIT = 4;
-
-        private volatile boolean mFinished;
-
-        private final Resources mResources;
-        private final SurfaceTexture mSurface;
-
-        private EGL10 mEgl;
-        private EGLDisplay mEglDisplay;
-        private EGLConfig mEglConfig;
-        private EGLContext mEglContext;
-        private EGLSurface mEglSurface;
-
-        RenderThread(Resources resources, SurfaceTexture surface) {
-            mResources = resources;
-            mSurface = surface;
-        }
-
-        private static final String sSimpleVS =
-                "attribute vec4 position;\n" +
-                "attribute vec2 texCoords;\n" +
-                "varying vec2 outTexCoords;\n" +
-                "\nvoid main(void) {\n" +
-                "    outTexCoords = texCoords;\n" +
-                "    gl_Position = position;\n" +
-                "}\n\n";
-        private static final String sSimpleFS =
-                "precision mediump float;\n\n" +
-                "varying vec2 outTexCoords;\n" +
-                "uniform sampler2D texture;\n" +
-                "\nvoid main(void) {\n" +
-                "    gl_FragColor = texture2D(texture, outTexCoords);\n" +
-                "}\n\n";
-
-        private static final int FLOAT_SIZE_BYTES = 4;
-        private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES;
-        private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0;
-        private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3;
-        private final float[] mTriangleVerticesData = {
-                // X, Y, Z, U, V
-                -1.0f, -1.0f, 0.0f, 0.0f, 0.0f,
-                 1.0f, -1.0f, 0.0f, 1.0f, 0.0f,
-                -1.0f,  1.0f, 0.0f, 0.0f, 1.0f,
-                 1.0f,  1.0f, 0.0f, 1.0f, 1.0f,
-        };
-
-        @Override
-        public void run() {
-            initGL();
-
-            FloatBuffer triangleVertices = ByteBuffer.allocateDirect(mTriangleVerticesData.length
-                    * FLOAT_SIZE_BYTES).order(ByteOrder.nativeOrder()).asFloatBuffer();
-            triangleVertices.put(mTriangleVerticesData).position(0);
-
-            int texture = loadTexture(R.drawable.large_photo);
-            int program = buildProgram(sSimpleVS, sSimpleFS);
-
-            int attribPosition = glGetAttribLocation(program, "position");
-            checkGlError();
-
-            int attribTexCoords = glGetAttribLocation(program, "texCoords");
-            checkGlError();
-
-            int uniformTexture = glGetUniformLocation(program, "texture");
-            checkGlError();
-
-            glBindTexture(GL_TEXTURE_2D, texture);
-            checkGlError();
-
-            glUseProgram(program);
-            checkGlError();
-
-            glEnableVertexAttribArray(attribPosition);
-            checkGlError();
-
-            glEnableVertexAttribArray(attribTexCoords);
-            checkGlError();
-
-            glUniform1i(uniformTexture, 0);
-            checkGlError();
-
-            // drawQuad
-            triangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);
-            glVertexAttribPointer(attribPosition, 3, GL_FLOAT, false,
-                    TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices);
-            checkGlError();
-
-            triangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET);
-            glVertexAttribPointer(attribTexCoords, 3, GL_FLOAT, false,
-                    TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices);
-            checkGlError();
-
-            glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
-            checkGlError();
-
-            while (!mFinished) {
-                checkCurrent();
-
-                glClear(GL_COLOR_BUFFER_BIT);
-                checkGlError();
-
-                glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
-                checkGlError();
-
-                if (!mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) {
-                    throw new RuntimeException("Cannot swap buffers");
-                }
-                checkEglError();
-
-                try {
-                    Thread.sleep(2000);
-                } catch (InterruptedException e) {
-                    // Ignore
-                }
-            }
-
-            finishGL();
-        }
-
-        private int loadTexture(int resource) {
-            int[] textures = new int[1];
-
-            glActiveTexture(GL_TEXTURE0);
-            glGenTextures(1, textures, 0);
-            checkGlError();
-
-            int texture = textures[0];
-            glBindTexture(GL_TEXTURE_2D, texture);
-            checkGlError();
-
-            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
-            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-
-            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
-            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
-
-            Bitmap bitmap = BitmapFactory.decodeResource(mResources, resource);
-
-            GLUtils.texImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bitmap, GL_UNSIGNED_BYTE, 0);
-            checkGlError();
-
-            bitmap.recycle();
-
-            return texture;
-        }
-
-        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(LOG_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(LOG_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(LOG_TAG, "EGL error = 0x" + Integer.toHexString(error));
-            }
-        }
-
-        private static void checkGlError() {
-            int error = glGetError();
-            if (error != GL_NO_ERROR) {
-                Log.w(LOG_TAG, "GL error = 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(LOG_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()));
-            }
-        }
-
-
-        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
-            };
-        }
-
-        void finish() {
-            mFinished = true;
-        }
-    }
 }
\ No newline at end of file
diff --git a/tests/UiBench/src/com/android/test/uibench/InvalidateActivity.java b/tests/UiBench/src/com/android/test/uibench/InvalidateActivity.java
new file mode 100644
index 0000000..93d67a6
--- /dev/null
+++ b/tests/UiBench/src/com/android/test/uibench/InvalidateActivity.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2015 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.test.uibench;
+
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.support.annotation.ColorInt;
+import android.support.v7.app.AppCompatActivity;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Tests invalidation performance by invalidating a large number of easily rendered views,
+ */
+public class InvalidateActivity extends AppCompatActivity {
+    public static class ColorView extends View {
+        @ColorInt
+        public int mColor;
+
+        public ColorView(Context context, AttributeSet attrs) {
+            super(context, attrs);
+        }
+
+        public void setColor(@ColorInt int color) {
+            mColor = color;
+            invalidate();
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            canvas.drawColor(mColor);
+        }
+    }
+
+    ColorView[][] mColorViews;
+
+    @SuppressWarnings("unused")
+    public void setColorValue(int colorValue) {
+        @ColorInt int a = Color.rgb(colorValue, 255 - colorValue, 255);
+        @ColorInt int b = Color.rgb(255, colorValue, 255 - colorValue);
+        for (int y = 0; y < mColorViews.length; y++) {
+            for (int x = 0; x < mColorViews[y].length; x++) {
+                mColorViews[y][x].setColor((x + y) % 2 == 0 ? a : b);
+            }
+        }
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_invalidate);
+
+        ViewGroup root = (ViewGroup) findViewById(R.id.invalidate_root);
+        for (int y = 0; y < root.getChildCount(); y++) {
+            ViewGroup row = (ViewGroup) root.getChildAt(y);
+            if (mColorViews == null) {
+                mColorViews = new ColorView[root.getChildCount()][row.getChildCount()];
+            }
+
+            for (int x = 0; x < row.getChildCount(); x++) {
+                mColorViews[y][x] = (ColorView) row.getChildAt(x);
+            }
+        }
+
+        ObjectAnimator animator = ObjectAnimator.ofInt(this, "colorValue", 0, 255);
+        animator.setRepeatMode(ValueAnimator.REVERSE);
+        animator.setRepeatCount(ValueAnimator.INFINITE);
+        animator.start();
+    }
+}
diff --git a/tests/UiBench/src/com/android/test/uibench/opengl/ImageFlipRenderThread.java b/tests/UiBench/src/com/android/test/uibench/opengl/ImageFlipRenderThread.java
new file mode 100644
index 0000000..119ce52
--- /dev/null
+++ b/tests/UiBench/src/com/android/test/uibench/opengl/ImageFlipRenderThread.java
@@ -0,0 +1,389 @@
+/*
+ * Copyright (C) 2015 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.test.uibench.opengl;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.SurfaceTexture;
+import android.opengl.GLUtils;
+import android.util.Log;
+
+import com.android.test.uibench.R;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+
+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 static android.opengl.GLES20.GL_CLAMP_TO_EDGE;
+import static android.opengl.GLES20.GL_COLOR_BUFFER_BIT;
+import static android.opengl.GLES20.GL_COMPILE_STATUS;
+import static android.opengl.GLES20.GL_FLOAT;
+import static android.opengl.GLES20.GL_FRAGMENT_SHADER;
+import static android.opengl.GLES20.GL_LINEAR;
+import static android.opengl.GLES20.GL_LINK_STATUS;
+import static android.opengl.GLES20.GL_NO_ERROR;
+import static android.opengl.GLES20.GL_RGBA;
+import static android.opengl.GLES20.GL_TEXTURE0;
+import static android.opengl.GLES20.GL_TEXTURE_2D;
+import static android.opengl.GLES20.GL_TEXTURE_MAG_FILTER;
+import static android.opengl.GLES20.GL_TEXTURE_MIN_FILTER;
+import static android.opengl.GLES20.GL_TEXTURE_WRAP_S;
+import static android.opengl.GLES20.GL_TEXTURE_WRAP_T;
+import static android.opengl.GLES20.GL_TRIANGLE_STRIP;
+import static android.opengl.GLES20.GL_TRUE;
+import static android.opengl.GLES20.GL_UNSIGNED_BYTE;
+import static android.opengl.GLES20.GL_VERTEX_SHADER;
+import static android.opengl.GLES20.glActiveTexture;
+import static android.opengl.GLES20.glAttachShader;
+import static android.opengl.GLES20.glBindTexture;
+import static android.opengl.GLES20.glClear;
+import static android.opengl.GLES20.glClearColor;
+import static android.opengl.GLES20.glCompileShader;
+import static android.opengl.GLES20.glCreateProgram;
+import static android.opengl.GLES20.glCreateShader;
+import static android.opengl.GLES20.glDeleteProgram;
+import static android.opengl.GLES20.glDeleteShader;
+import static android.opengl.GLES20.glDrawArrays;
+import static android.opengl.GLES20.glEnableVertexAttribArray;
+import static android.opengl.GLES20.glGenTextures;
+import static android.opengl.GLES20.glGetAttribLocation;
+import static android.opengl.GLES20.glGetError;
+import static android.opengl.GLES20.glGetProgramInfoLog;
+import static android.opengl.GLES20.glGetProgramiv;
+import static android.opengl.GLES20.glGetShaderInfoLog;
+import static android.opengl.GLES20.glGetShaderiv;
+import static android.opengl.GLES20.glGetUniformLocation;
+import static android.opengl.GLES20.glLinkProgram;
+import static android.opengl.GLES20.glShaderSource;
+import static android.opengl.GLES20.glTexParameteri;
+import static android.opengl.GLES20.glUniform1i;
+import static android.opengl.GLES20.glUseProgram;
+import static android.opengl.GLES20.glVertexAttribPointer;
+
+public class ImageFlipRenderThread extends Thread {
+    public static final String LOG_TAG = "GLTextureView";
+
+    static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
+    static final int EGL_OPENGL_ES2_BIT = 4;
+
+    private volatile boolean mFinished;
+
+    private final Resources mResources;
+    private final SurfaceTexture mSurface;
+
+    private EGL10 mEgl;
+    private EGLDisplay mEglDisplay;
+    private EGLConfig mEglConfig;
+    private EGLContext mEglContext;
+    private EGLSurface mEglSurface;
+
+    public ImageFlipRenderThread(Resources resources, SurfaceTexture surface) {
+        mResources = resources;
+        mSurface = surface;
+    }
+
+    private static final String sSimpleVS =
+            "attribute vec4 position;\n" +
+                    "attribute vec2 texCoords;\n" +
+                    "varying vec2 outTexCoords;\n" +
+                    "\nvoid main(void) {\n" +
+                    "    outTexCoords = texCoords;\n" +
+                    "    gl_Position = position;\n" +
+                    "}\n\n";
+    private static final String sSimpleFS =
+            "precision mediump float;\n\n" +
+                    "varying vec2 outTexCoords;\n" +
+                    "uniform sampler2D texture;\n" +
+                    "\nvoid main(void) {\n" +
+                    "    gl_FragColor = texture2D(texture, outTexCoords);\n" +
+                    "}\n\n";
+
+    private static final int FLOAT_SIZE_BYTES = 4;
+    private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES;
+    private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0;
+    private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3;
+    private final float[] mTriangleVerticesData = {
+            // X, Y, Z, U, V
+            -1.0f, -1.0f, 0.0f, 0.0f, 0.0f,
+            1.0f, -1.0f, 0.0f, 1.0f, 0.0f,
+            -1.0f, 1.0f, 0.0f, 0.0f, 1.0f,
+            1.0f, 1.0f, 0.0f, 1.0f, 1.0f,
+    };
+
+    @Override
+    public void run() {
+        initGL();
+
+        FloatBuffer triangleVertices = ByteBuffer.allocateDirect(mTriangleVerticesData.length
+                * FLOAT_SIZE_BYTES).order(ByteOrder.nativeOrder()).asFloatBuffer();
+        triangleVertices.put(mTriangleVerticesData).position(0);
+
+        int texture = loadTexture(R.drawable.large_photo);
+        int program = buildProgram(sSimpleVS, sSimpleFS);
+
+        int attribPosition = glGetAttribLocation(program, "position");
+        checkGlError();
+
+        int attribTexCoords = glGetAttribLocation(program, "texCoords");
+        checkGlError();
+
+        int uniformTexture = glGetUniformLocation(program, "texture");
+        checkGlError();
+
+        glBindTexture(GL_TEXTURE_2D, texture);
+        checkGlError();
+
+        glUseProgram(program);
+        checkGlError();
+
+        glEnableVertexAttribArray(attribPosition);
+        checkGlError();
+
+        glEnableVertexAttribArray(attribTexCoords);
+        checkGlError();
+
+        glUniform1i(uniformTexture, 0);
+        checkGlError();
+
+        // drawQuad
+        triangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);
+        glVertexAttribPointer(attribPosition, 3, GL_FLOAT, false,
+                TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices);
+        checkGlError();
+
+        triangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET);
+        glVertexAttribPointer(attribTexCoords, 3, GL_FLOAT, false,
+                TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices);
+        checkGlError();
+
+        glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+        checkGlError();
+
+        while (!mFinished) {
+            checkCurrent();
+
+            glClear(GL_COLOR_BUFFER_BIT);
+            checkGlError();
+
+            glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+            checkGlError();
+
+            if (!mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) {
+                throw new RuntimeException("Cannot swap buffers");
+            }
+            checkEglError();
+
+            try {
+                Thread.sleep(2000);
+            } catch (InterruptedException e) {
+                // Ignore
+            }
+        }
+
+        finishGL();
+    }
+
+    private int loadTexture(int resource) {
+        int[] textures = new int[1];
+
+        glActiveTexture(GL_TEXTURE0);
+        glGenTextures(1, textures, 0);
+        checkGlError();
+
+        int texture = textures[0];
+        glBindTexture(GL_TEXTURE_2D, texture);
+        checkGlError();
+
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+        Bitmap bitmap = BitmapFactory.decodeResource(mResources, resource);
+
+        GLUtils.texImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bitmap, GL_UNSIGNED_BYTE, 0);
+        checkGlError();
+
+        bitmap.recycle();
+
+        return texture;
+    }
+
+    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(LOG_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(LOG_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(LOG_TAG, "EGL error = 0x" + Integer.toHexString(error));
+        }
+    }
+
+    private static void checkGlError() {
+        int error = glGetError();
+        if (error != GL_NO_ERROR) {
+            Log.w(LOG_TAG, "GL error = 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(LOG_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()));
+        }
+    }
+
+
+    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
+        };
+    }
+
+    public void finish() {
+        mFinished = true;
+    }
+}