Merge "EGL cleanup tests"
diff --git a/tests/tests/media/src/android/media/cts/InputSurface.java b/tests/tests/media/src/android/media/cts/InputSurface.java
index 8d5c133..f23309d 100644
--- a/tests/tests/media/src/android/media/cts/InputSurface.java
+++ b/tests/tests/media/src/android/media/cts/InputSurface.java
@@ -38,7 +38,6 @@
     private static final boolean VERBOSE = false;
 
     private static final int EGL_RECORDABLE_ANDROID = 0x3142;
-    private static final int EGL_OPENGL_ES2_BIT = 4;
 
     private EGLDisplay mEGLDisplay;
     private EGLContext mEGLContext;
@@ -78,7 +77,7 @@
                 EGL14.EGL_RED_SIZE, 8,
                 EGL14.EGL_GREEN_SIZE, 8,
                 EGL14.EGL_BLUE_SIZE, 8,
-                EGL14.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+                EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
                 EGL_RECORDABLE_ANDROID, 1,
                 EGL14.EGL_NONE
         };
@@ -118,14 +117,10 @@
      * Surface that was passed to our constructor.
      */
     public void release() {
-        if (EGL14.eglGetCurrentContext().equals(mEGLContext)) {
-            // Clear the current context and surface to ensure they are discarded immediately.
-            EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE,
-                    EGL14.EGL_NO_CONTEXT);
+        if (mEGLDisplay != null) {
+            EGL14.eglReleaseThread();
+            EGL14.eglTerminate(mEGLDisplay);
         }
-        EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface);
-        EGL14.eglDestroyContext(mEGLDisplay, mEGLContext);
-        //EGL14.eglTerminate(mEGLDisplay);
 
         mSurface.release();
 
diff --git a/tests/tests/media/src/android/media/cts/OutputSurface.java b/tests/tests/media/src/android/media/cts/OutputSurface.java
index 5274627..8a299e2 100644
--- a/tests/tests/media/src/android/media/cts/OutputSurface.java
+++ b/tests/tests/media/src/android/media/cts/OutputSurface.java
@@ -18,23 +18,14 @@
 
 import android.graphics.SurfaceTexture;
 import android.opengl.EGL14;
-import android.opengl.GLES20;
-import android.opengl.GLES11Ext;
-import android.opengl.GLSurfaceView;
-import android.opengl.Matrix;
 import android.util.Log;
 import android.view.Surface;
 
-import java.nio.ByteBuffer;
-
 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 javax.microedition.khronos.opengles.GL10;
-
 
 
 /**
@@ -182,15 +173,9 @@
      * Discard all resources held by this class, notably the EGL context.
      */
     public void release() {
-        if (mEGL != null) {
-            if (mEGL.eglGetCurrentContext().equals(mEGLContext)) {
-                // Clear the current context and surface to ensure they are discarded immediately.
-                mEGL.eglMakeCurrent(mEGLDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE,
-                        EGL10.EGL_NO_CONTEXT);
-            }
-            mEGL.eglDestroySurface(mEGLDisplay, mEGLSurface);
-            mEGL.eglDestroyContext(mEGLDisplay, mEGLContext);
-            //mEGL.eglTerminate(mEGLDisplay);
+        if (mEGL != null && mEGLDisplay != null) {
+            mEGL.eglReleaseThread();
+            mEGL.eglTerminate(mEGLDisplay);
         }
 
         mSurface.release();
diff --git a/tests/tests/nativeopengl/libnativeopengltests/Android.mk b/tests/tests/nativeopengl/libnativeopengltests/Android.mk
index c9e10c4..e2bb08a 100644
--- a/tests/tests/nativeopengl/libnativeopengltests/Android.mk
+++ b/tests/tests/nativeopengl/libnativeopengltests/Android.mk
@@ -34,7 +34,8 @@
 LOCAL_SRC_FILES := \
         register.cpp \
         GLTestHelper.cpp \
-        tests/GLTest_test.cpp
+        tests/GLTest_test.cpp \
+        tests/EGLCleanup_test.cpp
 
 LOCAL_SHARED_LIBRARIES := libEGL \
                           libGLESv2 \
diff --git a/tests/tests/nativeopengl/standalone/jni/Android.mk b/tests/tests/nativeopengl/standalone/jni/Android.mk
index c23a1df..06a1436 100644
--- a/tests/tests/nativeopengl/standalone/jni/Android.mk
+++ b/tests/tests/nativeopengl/standalone/jni/Android.mk
@@ -41,7 +41,8 @@
 LOCAL_MODULE    := nativeopengltests
 LOCAL_SRC_FILES := GLTestHelper.cpp \
                    register.cpp \
-                   tests/GLTest_test.cpp
+                   tests/GLTest_test.cpp \
+                   tests/EGLCleanup_test.cpp
 
 LOCAL_SHARE_LIBRARIES := libgtest
 
diff --git a/tests/tests/nativeopengl/standalone/jni/tests/EGLCleanup_test.cpp b/tests/tests/nativeopengl/standalone/jni/tests/EGLCleanup_test.cpp
new file mode 100644
index 0000000..b5bd830
--- /dev/null
+++ b/tests/tests/nativeopengl/standalone/jni/tests/EGLCleanup_test.cpp
@@ -0,0 +1,344 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "EGLCleanup"
+#include <utils/Log.h>
+#include <utils/Thread.h>
+#include <jni.h>
+
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+
+#include <gtest/gtest.h>
+
+#include <GLTestHelper.h>
+
+#include <pthread.h>
+
+
+namespace android {
+
+/**
+ * Tests EGL cleanup edge cases.
+ */
+class EGLCleanupTest : public ::testing::Test {
+protected:
+    EGLCleanupTest() {}
+
+    virtual void SetUp() {
+        // Termination of a terminated display is defined to be a no-op.
+        // Android uses a refcounted implementation, so terminate it a few
+        // times to make sure it's really dead.  Without this, we might not
+        // get all the way into the driver eglTerminate implementation
+        // when we call eglTerminate.
+        EGLDisplay disp = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+        if (disp != EGL_NO_DISPLAY) {
+            ALOGD("speculative terminate");
+            eglTerminate(disp);
+            eglTerminate(disp);
+            eglTerminate(disp);
+        }
+    }
+    virtual void TearDown() {}
+};
+
+/**
+ * Perform an operation and then start a new thread.
+ *
+ * The trick here is that some code may be helpfully releasing storage in
+ * pthread_key destructors.  Those run after the thread returns out of the
+ * initial function, but before the thread fully exits.  We want them to
+ * run concurrently with the next thread's initialization so we can confirm
+ * that the specified behavior of eglTerminate vs. eglInitialize holds.
+ */
+class ChainedThread {
+public:
+    enum TestType {
+        TEST_CORRECT,
+        TEST_NO_RELEASE_CURRENT
+    };
+
+    ChainedThread(TestType testType) : mEglDisplay(EGL_NO_DISPLAY),
+            mEglSurface(EGL_NO_SURFACE), mEglContext(EGL_NO_CONTEXT),
+            mTestType(testType), mIteration(0), mResult(true) {
+        pthread_mutex_init(&mLock, NULL);
+        pthread_cond_init(&mCond, NULL);
+    }
+    ~ChainedThread() {
+        // could get fancy and clean up the mutex
+    }
+
+    /* start here */
+    bool start() {
+        lock();
+        bool result = startThread_l();
+        unlock();
+        return result;
+    }
+
+    /* waits until test is done; when finished, call getResult() */
+    bool waitForEnd() {
+        lock();
+        int err = pthread_cond_wait(&mCond, &mLock);
+        if (err != 0) {
+            ALOGW("pthread_cond_wait failed: %d", err);
+        }
+        unlock();
+        return err == 0;
+    }
+
+    /* returns the result; true means success */
+    bool getResult() {
+        return mResult;
+    }
+
+private:
+    enum { MAX_ITERATIONS = 1000 };
+
+    EGLDisplay mEglDisplay;
+    EGLSurface mEglSurface;
+    EGLContext mEglContext;
+
+    TestType mTestType;
+    int mIteration;
+    bool mResult;
+    pthread_mutex_t mLock;
+    pthread_cond_t mCond;
+
+    // Assertions set a flag in Java and return from the current method (which
+    // must be declared to return void).  They do not throw a C++ exception.
+    //
+    // Because we're running in a separate thread, which is not attached to
+    // the VM, the assert macros don't help us much.  We could attach to the
+    // VM (by linking to libdvm.so and calling a global function), but the
+    // assertions won't cause the test to stop, which makes them less valuable.
+    //
+    // So instead we just return a boolean out of functions that can fail.
+
+    /* call this to start the test */
+    bool startThread_l() {
+        pthread_attr_t attr;
+        pthread_attr_init(&attr);
+        pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+
+        pthread_t newThread;
+        int err = pthread_create(&newThread, &attr, ChainedThread::func,
+                (void*) this);
+        return (err == 0);
+    }
+
+    /* thread entry point */
+    static void* func(void* arg) {
+        ChainedThread* obj = static_cast<ChainedThread*>(arg);
+        obj->doWork();
+        return NULL;
+    }
+
+    bool lock() {
+        int err = pthread_mutex_lock(&mLock);
+        if (err != 0) {
+            ALOGW("pthread_mutex_lock failed: %d", err);
+        }
+        return err == 0;
+    }
+
+    bool unlock() {
+        int err = pthread_mutex_unlock(&mLock);
+        if (err != 0) {
+            ALOGW("pthread_mutex_unlock failed: %d", err);
+        }
+        return err == 0;
+    }
+
+    /* main worker */
+    void doWork() {
+        lock();
+
+        if ((mIteration % 25) == 0) {
+            ALOGD("iteration %d\n", mIteration);
+        }
+
+        mIteration++;
+        bool result = runTest_l();
+        if (!result) {
+            ALOGW("failed at iteration %d, stopping test", mIteration);
+            mResult = false;
+            mIteration = MAX_ITERATIONS;
+        }
+
+        if (mIteration < MAX_ITERATIONS) {
+            // still going, try to start the next one
+            if (!startThread_l()) {
+                ALOGW("Unable to start thread at iter=%d", mIteration);
+                mResult = false;
+                mIteration = MAX_ITERATIONS;
+            }
+        }
+
+        if (mIteration >= MAX_ITERATIONS) {
+            ALOGD("Test ending, signaling main thread");
+            pthread_cond_signal(&mCond);
+        }
+
+        unlock();
+    }
+
+    /* setup, use, release EGL */
+    bool runTest_l() {
+        if (!eglSetup()) {
+            return false;
+        }
+        if (!eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
+                mEglContext))
+        {
+            ALOGW("eglMakeCurrent failed: 0x%x", eglGetError());
+            return false;
+        }
+        if (!eglRelease_l()) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /*
+     * Sets up EGL.  Creates a 1280x720 pbuffer, which is large enough to
+     * cause a rapid and highly visible memory leak if we fail to discard it.
+     */
+    bool eglSetup() {
+        static const EGLint kConfigAttribs[] = {
+                EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
+                EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+                EGL_RED_SIZE, 8,
+                EGL_GREEN_SIZE, 8,
+                EGL_BLUE_SIZE, 8,
+                EGL_NONE
+        };
+        static const EGLint kContextAttribs[] = {
+                EGL_CONTEXT_CLIENT_VERSION, 2,
+                EGL_NONE
+        };
+        static const EGLint kPbufferAttribs[] = {
+                EGL_WIDTH, 1280,
+                EGL_HEIGHT, 720,
+                EGL_NONE
+        };
+
+        //usleep(25000);
+
+        mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+        if (mEglDisplay == EGL_NO_DISPLAY) {
+            ALOGW("eglGetDisplay failed: 0x%x", eglGetError());
+            return false;
+        }
+
+        EGLint majorVersion, minorVersion;
+        if (!eglInitialize(mEglDisplay, &majorVersion, &minorVersion)) {
+            ALOGW("eglInitialize failed: 0x%x", eglGetError());
+            return false;
+        }
+
+        EGLConfig eglConfig;
+        EGLint numConfigs = 0;
+        if (!eglChooseConfig(mEglDisplay, kConfigAttribs, &eglConfig,
+                1, &numConfigs)) {
+            ALOGW("eglChooseConfig failed: 0x%x", eglGetError());
+            return false;
+        }
+
+        mEglSurface = eglCreatePbufferSurface(mEglDisplay, eglConfig,
+                kPbufferAttribs);
+        if (mEglSurface == EGL_NO_SURFACE) {
+            ALOGW("eglCreatePbufferSurface failed: 0x%x", eglGetError());
+            return false;
+        }
+
+        mEglContext = eglCreateContext(mEglDisplay, eglConfig, EGL_NO_CONTEXT,
+                kContextAttribs);
+        if (mEglContext == EGL_NO_CONTEXT) {
+            ALOGW("eglCreateContext failed: 0x%x", eglGetError());
+            return false;
+        }
+
+        return true;
+    }
+
+    /*
+     * Releases EGL.  How we do that depends on the type of test we're
+     * running.
+     */
+    bool eglRelease_l() {
+        if (mEglDisplay == EGL_NO_DISPLAY) {
+            ALOGW("No display to release");
+            return false;
+        }
+
+        switch (mTestType) {
+        case TEST_CORRECT:
+            eglTerminate(mEglDisplay);
+            eglReleaseThread();
+            break;
+        case TEST_NO_RELEASE_CURRENT:
+            eglDestroyContext(mEglDisplay, mEglContext);
+            eglDestroySurface(mEglDisplay, mEglSurface);
+            eglTerminate(mEglDisplay);
+            break;
+        default:
+            ALOGE("Unknown test type %d", mTestType);
+            break;
+        }
+
+        int err = eglGetError();
+        if (err != EGL_SUCCESS) {
+            ALOGW("eglRelease failed: 0x%x", err);
+            return false;
+        }
+        return true;
+    }
+};
+
+
+/* do things correctly */
+TEST_F(EGLCleanupTest, TestCorrect) {
+    ALOGI("Starting TEST_CORRECT");
+    ChainedThread cht(ChainedThread::TEST_CORRECT);
+
+    // start initial thread
+    ASSERT_TRUE(cht.start());
+
+    // wait for the end
+    cht.waitForEnd();
+    bool result = cht.getResult();
+    ASSERT_TRUE(result);
+}
+
+#if 0
+/* try it without un-currenting the surfaces and context */
+TEST_F(EGLCleanupTest, TestNoReleaseCurrent) {
+    ALOGI("Starting TEST_NO_RELEASE_CURRENT");
+    ChainedThread cht(ChainedThread::TEST_NO_RELEASE_CURRENT);
+
+    // start initial thread
+    ASSERT_TRUE(cht.start());
+
+    // wait for the end
+    cht.waitForEnd();
+    bool result = cht.getResult();
+    ASSERT_TRUE(result);
+}
+#endif
+
+} // namespace android
diff --git a/tests/tests/opengl/src/android/opengl/cts/WrapperTest.java b/tests/tests/opengl/src/android/opengl/cts/WrapperTest.java
index acc56fc..03677d2 100644
--- a/tests/tests/opengl/src/android/opengl/cts/WrapperTest.java
+++ b/tests/tests/opengl/src/android/opengl/cts/WrapperTest.java
@@ -93,7 +93,7 @@
             // good
         }
 
-        eglRelease(true);
+        eglRelease();
     }
 
     /**
@@ -149,7 +149,7 @@
             // good
         }
 
-        eglRelease(true);
+        eglRelease();
     }
 
     /**
@@ -174,7 +174,7 @@
                     if (!EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) {
                         throw new RuntimeException("eglMakeCurrent failed");
                     }
-                    eglRelease(false);
+                    eglRelease();
                 } catch (Throwable th) {
                     mThrowable = th;
                 }
@@ -281,18 +281,16 @@
     }
 
     /**
-     * Releases EGL goodies.  If switchCurrent is true, this will use eglMakeCurrent to switch
-     * away from the current surface+context before destroying them.
+     * Releases EGL goodies.
      */
-    private void eglRelease(boolean switchCurrent) {
-        if (switchCurrent) {
-            // Clear the current context and surface to ensure they are discarded immediately.
-            EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE,
-                    EGL14.EGL_NO_CONTEXT);
+    private void eglRelease() {
+        // Terminating the display will release most objects, but won't discard the current
+        // surfaces and context until we release the thread.  It shouldn't matter what order
+        // we do these in.
+        if (mEGLDisplay != null) {
+            EGL14.eglTerminate(mEGLDisplay);
+            EGL14.eglReleaseThread();
         }
-        EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface);
-        EGL14.eglDestroyContext(mEGLDisplay, mEGLContext);
-        //EGL14.eglTerminate(mEGLDisplay);
 
         // null everything out so future attempts to use this object will cause an NPE
         mEGLDisplay = null;