OSDN Git Service

SurfaceTexture: add context attach & detach
authorJamie Gennis <jgennis@google.com>
Thu, 29 Mar 2012 02:05:54 +0000 (19:05 -0700)
committerJamie Gennis <jgennis@google.com>
Fri, 30 Mar 2012 23:47:28 +0000 (16:47 -0700)
This change adds the detachFromContext and attachToContext methods to
SurfaceTexture.  These methods allow the SurfaceTexture to switch from
one consumer GLES context to another.  This change also includes a few
cleanups to the error return codes in updateTexImage.

Change-Id: I0df1eb599aa7b6f58f07431f242f8f09269559ed

include/gui/SurfaceTexture.h
libs/gui/SurfaceTexture.cpp
libs/gui/tests/SurfaceTexture_test.cpp

index cd490f2..496cfba 100644 (file)
@@ -55,8 +55,7 @@ public:
     };
 
     // SurfaceTexture constructs a new SurfaceTexture object. tex indicates the
-    // name of the OpenGL ES texture to which images are to be streamed. This
-    // texture name cannot be changed once the SurfaceTexture is created.
+    // name of the OpenGL ES texture to which images are to be streamed.
     // allowSynchronousMode specifies whether or not synchronous mode can be
     // enabled. texTarget specifies the OpenGL ES texture target to which the
     // texture will be bound in updateTexImage. useFenceSync specifies whether
@@ -64,6 +63,21 @@ public:
     // is enabled at compile-time. A custom bufferQueue can be specified
     // if behavior for queue/dequeue/connect etc needs to be customized.
     // Otherwise a default BufferQueue will be created and used.
+    //
+    // For legacy reasons, the SurfaceTexture is created in a state where it is
+    // considered attached to an OpenGL ES context for the purposes of the
+    // attachToContext and detachFromContext methods. However, despite being
+    // considered "attached" to a context, the specific OpenGL ES context
+    // doesn't get latched until the first call to updateTexImage. After that
+    // point, all calls to updateTexImage must be made with the same OpenGL ES
+    // context current.
+    //
+    // A SurfaceTexture may be detached from one OpenGL ES context and then
+    // attached to a different context using the detachFromContext and
+    // attachToContext methods, respectively. The intention of these methods is
+    // purely to allow a SurfaceTexture to be transferred from one consumer
+    // context to another. If such a transfer is not needed there is no
+    // requirement that either of these methods be called.
     SurfaceTexture(GLuint tex, bool allowSynchronousMode = true,
             GLenum texTarget = GL_TEXTURE_EXTERNAL_OES, bool useFenceSync = true,
             const sp<BufferQueue> &bufferQueue = 0);
@@ -175,8 +189,37 @@ public:
     virtual status_t connect(int api,
                 uint32_t* outWidth, uint32_t* outHeight, uint32_t* outTransform);
 
+    // getBufferQueue returns the BufferQueue object to which this
+    // SurfaceTexture is connected.
     sp<BufferQueue> getBufferQueue() const;
 
+    // detachFromContext detaches the SurfaceTexture from the calling thread's
+    // current OpenGL ES context.  This context must be the same as the context
+    // that was current for previous calls to updateTexImage.
+    //
+    // Detaching a SurfaceTexture from an OpenGL ES context will result in the
+    // deletion of the OpenGL ES texture object into which the images were being
+    // streamed.  After a SurfaceTexture has been detached from the OpenGL ES
+    // context calls to updateTexImage will fail returning INVALID_OPERATION
+    // until the SurfaceTexture is attached to a new OpenGL ES context using the
+    // attachToContext method.
+    status_t detachFromContext();
+
+    // attachToContext attaches a SurfaceTexture that is currently in the
+    // 'detached' state to the current OpenGL ES context.  A SurfaceTexture is
+    // in the 'detached' state iff detachFromContext has successfully been
+    // called and no calls to attachToContext have succeeded since the last
+    // detachFromContext call.  Calls to attachToContext made on a
+    // SurfaceTexture that is not in the 'detached' state will result in an
+    // INVALID_OPERATION error.
+    //
+    // The tex argument specifies the OpenGL ES texture object name in the
+    // new context into which the image contents will be streamed.  A successful
+    // call to attachToContext will result in this texture object being bound to
+    // the texture target and populated with the image contents that were
+    // current at the time of the last call to detachFromContext.
+    status_t attachToContext(GLuint tex);
+
     // dump our state in a String
     virtual void dump(String8& result) const;
     virtual void dump(String8& result, const char* prefix, char* buffer, size_t SIZE) const;
@@ -209,6 +252,12 @@ private:
     // to compute this matrix and stores it in mCurrentTransformMatrix.
     void computeCurrentTransformMatrix();
 
+    // syncForReleaseLocked performs the synchronization needed to release the
+    // current slot from an OpenGL ES context.  If needed it will set the
+    // current slot's fence to guard against a producer accessing the buffer
+    // before the outstanding accesses have completed.
+    status_t syncForReleaseLocked(EGLDisplay dpy);
+
     // mCurrentTextureBuf is the graphic buffer of the current texture. It's
     // possible that this buffer is not associated with any buffer slot, so we
     // must track it separately in order to support the getCurrentBuffer method.
@@ -237,8 +286,8 @@ private:
 
     // mTexName is the name of the OpenGL texture to which streamed images will
     // be bound when updateTexImage is called. It is set at construction time
-    // changed with a call to setTexName.
-    const GLuint mTexName;
+    // and can be changed with a call to attachToContext.
+    GLuint mTexName;
 
     // mUseFenceSync indicates whether creation of the EGL_KHR_fence_sync
     // extension should be used to prevent buffers from being dequeued before
@@ -277,13 +326,14 @@ private:
 
     // mEglDisplay is the EGLDisplay with which this SurfaceTexture is currently
     // associated.  It is intialized to EGL_NO_DISPLAY and gets set to the
-    // current display when updateTexImage is called for the first time.
+    // current display when updateTexImage is called for the first time and when
+    // attachToContext is called.
     EGLDisplay mEglDisplay;
 
     // mEglContext is the OpenGL ES context with which this SurfaceTexture is
     // currently associated.  It is initialized to EGL_NO_CONTEXT and gets set
     // to the current GL context when updateTexImage is called for the first
-    // time.
+    // time and when attachToContext is called.
     EGLContext mEglContext;
 
     // mEGLSlots stores the buffers that have been allocated by the BufferQueue
@@ -323,6 +373,14 @@ private:
     // if none is supplied
     sp<BufferQueue> mBufferQueue;
 
+    // mAttached indicates whether the SurfaceTexture is currently attached to
+    // an OpenGL ES context.  For legacy reasons, this is initialized to true,
+    // indicating that the SurfaceTexture is considered to be attached to
+    // whatever context is current at the time of the first updateTexImage call.
+    // It is set to false by detachFromContext, and then set to true again by
+    // attachToContext.
+    bool mAttached;
+
     // mMutex is the mutex used to prevent concurrent access to the member
     // variables of SurfaceTexture objects. It must be locked whenever the
     // member variables are accessed.
index 07248f6..18c86fa 100644 (file)
@@ -118,7 +118,8 @@ SurfaceTexture::SurfaceTexture(GLuint tex, bool allowSynchronousMode,
     mEglDisplay(EGL_NO_DISPLAY),
     mEglContext(EGL_NO_CONTEXT),
     mAbandoned(false),
-    mCurrentTexture(BufferQueue::INVALID_BUFFER_SLOT)
+    mCurrentTexture(BufferQueue::INVALID_BUFFER_SLOT),
+    mAttached(true)
 {
     // Choose a name using the PID and a process-unique ID.
     mName = String8::format("unnamed-%d-%d", getpid(), createProcessUniqueId());
@@ -176,21 +177,29 @@ status_t SurfaceTexture::updateTexImage() {
     Mutex::Autolock lock(mMutex);
 
     if (mAbandoned) {
-        ST_LOGE("calling updateTexImage() on an abandoned SurfaceTexture");
+        ST_LOGE("updateTexImage: SurfaceTexture is abandoned!");
         return NO_INIT;
     }
 
+    if (!mAttached) {
+        ST_LOGE("updateTexImage: SurfaceTexture is not attached to an OpenGL "
+                "ES context");
+        return INVALID_OPERATION;
+    }
+
     EGLDisplay dpy = eglGetCurrentDisplay();
     EGLContext ctx = eglGetCurrentContext();
 
-    if (mEglDisplay != dpy && mEglDisplay != EGL_NO_DISPLAY) {
+    if ((mEglDisplay != dpy && mEglDisplay != EGL_NO_DISPLAY) ||
+            dpy == EGL_NO_DISPLAY) {
         ST_LOGE("updateTexImage: invalid current EGLDisplay");
-        return -EINVAL;
+        return INVALID_OPERATION;
     }
 
-    if (mEglContext != ctx && mEglContext != EGL_NO_CONTEXT) {
+    if ((mEglContext != ctx && mEglContext != EGL_NO_CONTEXT) ||
+            ctx == EGL_NO_CONTEXT) {
         ST_LOGE("updateTexImage: invalid current EGLContext");
-        return -EINVAL;
+        return INVALID_OPERATION;
     }
 
     mEglDisplay = dpy;
@@ -216,7 +225,7 @@ status_t SurfaceTexture::updateTexImage() {
         EGLImageKHR image = mEGLSlots[buf].mEglImage;
         if (image == EGL_NO_IMAGE_KHR) {
             if (item.mGraphicBuffer == 0) {
-                ST_LOGE("buffer at slot %d is null", buf);
+                ST_LOGE("updateTexImage: buffer at slot %d is null", buf);
                 return BAD_VALUE;
             }
             image = createImage(dpy, item.mGraphicBuffer);
@@ -224,7 +233,7 @@ status_t SurfaceTexture::updateTexImage() {
             if (image == EGL_NO_IMAGE_KHR) {
                 // NOTE: if dpy was invalid, createImage() is guaranteed to
                 // fail. so we'd end up here.
-                return -EINVAL;
+                return UNKNOWN_ERROR;
             }
         }
 
@@ -236,31 +245,23 @@ status_t SurfaceTexture::updateTexImage() {
         glBindTexture(mTexTarget, mTexName);
         glEGLImageTargetTexture2DOES(mTexTarget, (GLeglImageOES)image);
 
-        bool failed = false;
+        status_t err = OK;
         while ((error = glGetError()) != GL_NO_ERROR) {
-            ST_LOGE("error binding external texture image %p (slot %d): %#04x",
-                    image, buf, error);
-            failed = true;
+            ST_LOGE("updateTexImage: error binding external texture image %p "
+                    "(slot %d): %#04x", image, buf, error);
+            err = UNKNOWN_ERROR;
         }
-        if (failed) {
-            mBufferQueue->releaseBuffer(buf, dpy, mEGLSlots[buf].mFence);
-            return -EINVAL;
+
+        if (err == OK) {
+            err = syncForReleaseLocked(dpy);
         }
 
-        if (mCurrentTexture != BufferQueue::INVALID_BUFFER_SLOT) {
-            if (mUseFenceSync) {
-                EGLSyncKHR fence = eglCreateSyncKHR(dpy, EGL_SYNC_FENCE_KHR,
-                        NULL);
-                if (fence == EGL_NO_SYNC_KHR) {
-                    ALOGE("updateTexImage: error creating fence: %#x",
-                            eglGetError());
-                    mBufferQueue->releaseBuffer(buf, dpy,
-                            mEGLSlots[buf].mFence);
-                    return -EINVAL;
-                }
-                glFlush();
-                mEGLSlots[mCurrentTexture].mFence = fence;
-            }
+        if (err != OK) {
+            // Release the buffer we just acquired.  It's not safe to
+            // release the old buffer, so instead we just drop the new frame.
+            mBufferQueue->releaseBuffer(buf, dpy, mEGLSlots[buf].mFence);
+            mEGLSlots[buf].mFence = EGL_NO_SYNC_KHR;
+            return err;
         }
 
         ST_LOGV("updateTexImage: (slot=%d buf=%p) -> (slot=%d buf=%p)",
@@ -268,9 +269,12 @@ status_t SurfaceTexture::updateTexImage() {
                 mCurrentTextureBuf != NULL ? mCurrentTextureBuf->handle : 0,
                 buf, item.mGraphicBuffer != NULL ? item.mGraphicBuffer->handle : 0);
 
-        // release old buffer
-        mBufferQueue->releaseBuffer(mCurrentTexture, dpy,
-                mEGLSlots[mCurrentTexture].mFence);
+        // Release the old buffer
+        if (mCurrentTexture != BufferQueue::INVALID_BUFFER_SLOT) {
+            mBufferQueue->releaseBuffer(mCurrentTexture, dpy,
+                    mEGLSlots[mCurrentTexture].mFence);
+            mEGLSlots[mCurrentTexture].mFence = EGL_NO_SYNC_KHR;
+        }
 
         // Update the SurfaceTexture state.
         mCurrentTexture = buf;
@@ -280,10 +284,6 @@ status_t SurfaceTexture::updateTexImage() {
         mCurrentScalingMode = item.mScalingMode;
         mCurrentTimestamp = item.mTimestamp;
         computeCurrentTransformMatrix();
-
-        // Now that we've passed the point at which failures can happen,
-        // it's safe to remove the buffer from the front of the queue.
-
     } else {
         // We always bind the texture even if we don't update its contents.
         glBindTexture(mTexTarget, mTexName);
@@ -292,6 +292,168 @@ status_t SurfaceTexture::updateTexImage() {
     return OK;
 }
 
+status_t SurfaceTexture::detachFromContext() {
+    ATRACE_CALL();
+    ST_LOGV("detachFromContext");
+    Mutex::Autolock lock(mMutex);
+
+    if (mAbandoned) {
+        ST_LOGE("detachFromContext: abandoned SurfaceTexture");
+        return NO_INIT;
+    }
+
+    if (!mAttached) {
+        ST_LOGE("detachFromContext: SurfaceTexture is not attached to a "
+                "context");
+        return INVALID_OPERATION;
+    }
+
+    EGLDisplay dpy = eglGetCurrentDisplay();
+    EGLContext ctx = eglGetCurrentContext();
+
+    if (mEglDisplay != dpy && mEglDisplay != EGL_NO_DISPLAY) {
+        ST_LOGE("detachFromContext: invalid current EGLDisplay");
+        return INVALID_OPERATION;
+    }
+
+    if (mEglContext != ctx && mEglContext != EGL_NO_CONTEXT) {
+        ST_LOGE("detachFromContext: invalid current EGLContext");
+        return INVALID_OPERATION;
+    }
+
+    if (dpy != EGL_NO_DISPLAY && ctx != EGL_NO_CONTEXT) {
+        status_t err = syncForReleaseLocked(dpy);
+        if (err != OK) {
+            return err;
+        }
+
+        glDeleteTextures(1, &mTexName);
+    }
+
+    mEglDisplay = EGL_NO_DISPLAY;
+    mEglContext = EGL_NO_CONTEXT;
+    mAttached = false;
+
+    return OK;
+}
+
+status_t SurfaceTexture::attachToContext(GLuint tex) {
+    ATRACE_CALL();
+    ST_LOGV("attachToContext");
+    Mutex::Autolock lock(mMutex);
+
+    if (mAbandoned) {
+        ST_LOGE("attachToContext: abandoned SurfaceTexture");
+        return NO_INIT;
+    }
+
+    if (mAttached) {
+        ST_LOGE("attachToContext: SurfaceTexture is already attached to a "
+                "context");
+        return INVALID_OPERATION;
+    }
+
+    EGLDisplay dpy = eglGetCurrentDisplay();
+    EGLContext ctx = eglGetCurrentContext();
+
+    if (dpy == EGL_NO_DISPLAY) {
+        ST_LOGE("attachToContext: invalid current EGLDisplay");
+        return INVALID_OPERATION;
+    }
+
+    if (ctx == EGL_NO_CONTEXT) {
+        ST_LOGE("attachToContext: invalid current EGLContext");
+        return INVALID_OPERATION;
+    }
+
+    // We need to bind the texture regardless of whether there's a current
+    // buffer.
+    glBindTexture(mTexTarget, tex);
+
+    if (mCurrentTextureBuf != NULL) {
+        // If the current buffer is no longer associated with a slot, then it
+        // doesn't have an EGLImage.  In that case we create one now, but we also
+        // destroy it once we've used it to attach the buffer to the OpenGL ES
+        // texture.
+        bool imageNeedsDestroy = false;
+        EGLImageKHR image = EGL_NO_IMAGE_KHR;
+        if (mCurrentTexture != BufferQueue::INVALID_BUFFER_SLOT) {
+            image = mEGLSlots[mCurrentTexture].mEglImage;
+            imageNeedsDestroy = false;
+        } else {
+            image = createImage(dpy, mCurrentTextureBuf);
+            if (image == EGL_NO_IMAGE_KHR) {
+                return UNKNOWN_ERROR;
+            }
+            imageNeedsDestroy = true;
+        }
+
+        // Attach the current buffer to the GL texture.
+        glEGLImageTargetTexture2DOES(mTexTarget, (GLeglImageOES)image);
+
+        GLint error;
+        status_t err = OK;
+        while ((error = glGetError()) != GL_NO_ERROR) {
+            ST_LOGE("attachToContext: error binding external texture image %p "
+                    "(slot %d): %#04x", image, mCurrentTexture, error);
+            err = UNKNOWN_ERROR;
+        }
+
+        if (imageNeedsDestroy) {
+            eglDestroyImageKHR(dpy, image);
+        }
+
+        if (err != OK) {
+            return err;
+        }
+    }
+
+    mEglDisplay = dpy;
+    mEglContext = ctx;
+    mTexName = tex;
+    mAttached = true;
+
+    return OK;
+}
+
+status_t SurfaceTexture::syncForReleaseLocked(EGLDisplay dpy) {
+    ST_LOGV("syncForReleaseLocked");
+
+    if (mUseFenceSync && mCurrentTexture != BufferQueue::INVALID_BUFFER_SLOT) {
+        EGLSyncKHR fence = mEGLSlots[mCurrentTexture].mFence;
+        if (fence != EGL_NO_SYNC_KHR) {
+            // There is already a fence for the current slot.  We need to wait
+            // on that before replacing it with another fence to ensure that all
+            // outstanding buffer accesses have completed before the producer
+            // accesses it.
+            EGLint result = eglClientWaitSyncKHR(dpy, fence, 0, 1000000000);
+            if (result == EGL_FALSE) {
+                ST_LOGE("syncForReleaseLocked: error waiting for previous "
+                        "fence: %#x", eglGetError());
+                return UNKNOWN_ERROR;
+            } else if (result == EGL_TIMEOUT_EXPIRED_KHR) {
+                ST_LOGE("syncForReleaseLocked: timeout waiting for previous "
+                        "fence");
+                return TIMED_OUT;
+            }
+            eglDestroySyncKHR(dpy, fence);
+        }
+
+        // Create a fence for the outstanding accesses in the current OpenGL ES
+        // context.
+        fence = eglCreateSyncKHR(dpy, EGL_SYNC_FENCE_KHR, NULL);
+        if (fence == EGL_NO_SYNC_KHR) {
+            ST_LOGE("syncForReleaseLocked: error creating fence: %#x",
+                    eglGetError());
+            return UNKNOWN_ERROR;
+        }
+        glFlush();
+        mEGLSlots[mCurrentTexture].mFence = fence;
+    }
+
+    return OK;
+}
+
 bool SurfaceTexture::isExternalFormat(uint32_t format)
 {
     switch (format) {
index d6357ca..bf347c6 100644 (file)
@@ -191,100 +191,6 @@ protected:
         return 512;
     }
 
-    void loadShader(GLenum shaderType, const char* pSource, GLuint* outShader) {
-        GLuint shader = glCreateShader(shaderType);
-        ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
-        if (shader) {
-            glShaderSource(shader, 1, &pSource, NULL);
-            ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
-            glCompileShader(shader);
-            ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
-            GLint compiled = 0;
-            glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
-            ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
-            if (!compiled) {
-                GLint infoLen = 0;
-                glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
-                ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
-                if (infoLen) {
-                    char* buf = (char*) malloc(infoLen);
-                    if (buf) {
-                        glGetShaderInfoLog(shader, infoLen, NULL, buf);
-                        printf("Shader compile log:\n%s\n", buf);
-                        free(buf);
-                        FAIL();
-                    }
-                } else {
-                    char* buf = (char*) malloc(0x1000);
-                    if (buf) {
-                        glGetShaderInfoLog(shader, 0x1000, NULL, buf);
-                        printf("Shader compile log:\n%s\n", buf);
-                        free(buf);
-                        FAIL();
-                    }
-                }
-                glDeleteShader(shader);
-                shader = 0;
-            }
-        }
-        ASSERT_TRUE(shader != 0);
-        *outShader = shader;
-    }
-
-    void createProgram(const char* pVertexSource, const char* pFragmentSource,
-            GLuint* outPgm) {
-        GLuint vertexShader, fragmentShader;
-        {
-            SCOPED_TRACE("compiling vertex shader");
-            loadShader(GL_VERTEX_SHADER, pVertexSource, &vertexShader);
-            if (HasFatalFailure()) {
-                return;
-            }
-        }
-        {
-            SCOPED_TRACE("compiling fragment shader");
-            loadShader(GL_FRAGMENT_SHADER, pFragmentSource, &fragmentShader);
-            if (HasFatalFailure()) {
-                return;
-            }
-        }
-
-        GLuint program = glCreateProgram();
-        ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
-        if (program) {
-            glAttachShader(program, vertexShader);
-            ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
-            glAttachShader(program, fragmentShader);
-            ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
-            glLinkProgram(program);
-            GLint linkStatus = GL_FALSE;
-            glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
-            if (linkStatus != GL_TRUE) {
-                GLint bufLength = 0;
-                glGetProgramiv(program, GL_INFO_LOG_LENGTH, &bufLength);
-                if (bufLength) {
-                    char* buf = (char*) malloc(bufLength);
-                    if (buf) {
-                        glGetProgramInfoLog(program, bufLength, NULL, buf);
-                        printf("Program link log:\n%s\n", buf);
-                        free(buf);
-                        FAIL();
-                    }
-                }
-                glDeleteProgram(program);
-                program = 0;
-            }
-        }
-        glDeleteShader(vertexShader);
-        glDeleteShader(fragmentShader);
-        ASSERT_TRUE(program != 0);
-        *outPgm = program;
-    }
-
-    static int abs(int value) {
-        return value > 0 ? value : -value;
-    }
-
     ::testing::AssertionResult checkPixel(int x, int y, int r,
             int g, int b, int a, int tolerance=2) {
         GLubyte pixel[4];
@@ -340,6 +246,98 @@ protected:
     EGLConfig  mGlConfig;
 };
 
+static void loadShader(GLenum shaderType, const char* pSource,
+        GLuint* outShader) {
+    GLuint shader = glCreateShader(shaderType);
+    ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+    if (shader) {
+        glShaderSource(shader, 1, &pSource, NULL);
+        ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+        glCompileShader(shader);
+        ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+        GLint compiled = 0;
+        glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
+        ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+        if (!compiled) {
+            GLint infoLen = 0;
+            glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
+            ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+            if (infoLen) {
+                char* buf = (char*) malloc(infoLen);
+                if (buf) {
+                    glGetShaderInfoLog(shader, infoLen, NULL, buf);
+                    printf("Shader compile log:\n%s\n", buf);
+                    free(buf);
+                    FAIL();
+                }
+            } else {
+                char* buf = (char*) malloc(0x1000);
+                if (buf) {
+                    glGetShaderInfoLog(shader, 0x1000, NULL, buf);
+                    printf("Shader compile log:\n%s\n", buf);
+                    free(buf);
+                    FAIL();
+                }
+            }
+            glDeleteShader(shader);
+            shader = 0;
+        }
+    }
+    ASSERT_TRUE(shader != 0);
+    *outShader = shader;
+}
+
+static void createProgram(const char* pVertexSource,
+        const char* pFragmentSource, GLuint* outPgm) {
+    GLuint vertexShader, fragmentShader;
+    {
+        SCOPED_TRACE("compiling vertex shader");
+        ASSERT_NO_FATAL_FAILURE(loadShader(GL_VERTEX_SHADER, pVertexSource,
+                &vertexShader));
+    }
+    {
+        SCOPED_TRACE("compiling fragment shader");
+        ASSERT_NO_FATAL_FAILURE(loadShader(GL_FRAGMENT_SHADER, pFragmentSource,
+                &fragmentShader));
+    }
+
+    GLuint program = glCreateProgram();
+    ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+    if (program) {
+        glAttachShader(program, vertexShader);
+        ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+        glAttachShader(program, fragmentShader);
+        ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+        glLinkProgram(program);
+        GLint linkStatus = GL_FALSE;
+        glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
+        if (linkStatus != GL_TRUE) {
+            GLint bufLength = 0;
+            glGetProgramiv(program, GL_INFO_LOG_LENGTH, &bufLength);
+            if (bufLength) {
+                char* buf = (char*) malloc(bufLength);
+                if (buf) {
+                    glGetProgramInfoLog(program, bufLength, NULL, buf);
+                    printf("Program link log:\n%s\n", buf);
+                    free(buf);
+                    FAIL();
+                }
+            }
+            glDeleteProgram(program);
+            program = 0;
+        }
+    }
+    glDeleteShader(vertexShader);
+    glDeleteShader(fragmentShader);
+    ASSERT_TRUE(program != 0);
+    *outPgm = program;
+}
+
+static int abs(int value) {
+    return value > 0 ? value : -value;
+}
+
+
 // XXX: Code above this point should live elsewhere
 
 class SurfaceTextureGLTest : public GLTest {
@@ -351,43 +349,8 @@ protected:
         mST = new SurfaceTexture(TEX_ID);
         mSTC = new SurfaceTextureClient(mST);
         mANW = mSTC;
-
-        const char vsrc[] =
-            "attribute vec4 vPosition;\n"
-            "varying vec2 texCoords;\n"
-            "uniform mat4 texMatrix;\n"
-            "void main() {\n"
-            "  vec2 vTexCoords = 0.5 * (vPosition.xy + vec2(1.0, 1.0));\n"
-            "  texCoords = (texMatrix * vec4(vTexCoords, 0.0, 1.0)).xy;\n"
-            "  gl_Position = vPosition;\n"
-            "}\n";
-
-        const char fsrc[] =
-            "#extension GL_OES_EGL_image_external : require\n"
-            "precision mediump float;\n"
-            "uniform samplerExternalOES texSampler;\n"
-            "varying vec2 texCoords;\n"
-            "void main() {\n"
-            "  gl_FragColor = texture2D(texSampler, texCoords);\n"
-            "}\n";
-
-        {
-            SCOPED_TRACE("creating shader program");
-            createProgram(vsrc, fsrc, &mPgm);
-            if (HasFatalFailure()) {
-                return;
-            }
-        }
-
-        mPositionHandle = glGetAttribLocation(mPgm, "vPosition");
-        ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
-        ASSERT_NE(-1, mPositionHandle);
-        mTexSamplerHandle = glGetUniformLocation(mPgm, "texSampler");
-        ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
-        ASSERT_NE(-1, mTexSamplerHandle);
-        mTexMatrixHandle = glGetUniformLocation(mPgm, "texMatrix");
-        ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
-        ASSERT_NE(-1, mTexMatrixHandle);
+        mTextureRenderer = new TextureRenderer(TEX_ID, mST);
+        ASSERT_NO_FATAL_FAILURE(mTextureRenderer->SetUp());
     }
 
     virtual void TearDown() {
@@ -397,50 +360,106 @@ protected:
         GLTest::TearDown();
     }
 
-    // drawTexture draws the SurfaceTexture over the entire GL viewport.
     void drawTexture() {
-        const GLfloat triangleVertices[] = {
-            -1.0f, 1.0f,
-            -1.0f, -1.0f,
-            1.0f, -1.0f,
-            1.0f, 1.0f,
-        };
-
-        glVertexAttribPointer(mPositionHandle, 2, GL_FLOAT, GL_FALSE, 0,
-                triangleVertices);
-        ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
-        glEnableVertexAttribArray(mPositionHandle);
-        ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+        mTextureRenderer->drawTexture();
+    }
 
-        glUseProgram(mPgm);
-        glUniform1i(mTexSamplerHandle, 0);
-        ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
-        glBindTexture(GL_TEXTURE_EXTERNAL_OES, TEX_ID);
-        ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+    class TextureRenderer: public RefBase {
+    public:
+        TextureRenderer(GLuint texName, const sp<SurfaceTexture>& st):
+                mTexName(texName),
+                mST(st) {
+        }
 
-        // XXX: These calls are not needed for GL_TEXTURE_EXTERNAL_OES as
-        // they're setting the defautls for that target, but when hacking things
-        // to use GL_TEXTURE_2D they are needed to achieve the same behavior.
-        glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER,
-                GL_LINEAR);
-        ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
-        glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER,
-                GL_LINEAR);
-        ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
-        glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S,
-                GL_CLAMP_TO_EDGE);
-        ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
-        glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T,
-                GL_CLAMP_TO_EDGE);
-        ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+        void SetUp() {
+            const char vsrc[] =
+                "attribute vec4 vPosition;\n"
+                "varying vec2 texCoords;\n"
+                "uniform mat4 texMatrix;\n"
+                "void main() {\n"
+                "  vec2 vTexCoords = 0.5 * (vPosition.xy + vec2(1.0, 1.0));\n"
+                "  texCoords = (texMatrix * vec4(vTexCoords, 0.0, 1.0)).xy;\n"
+                "  gl_Position = vPosition;\n"
+                "}\n";
+
+            const char fsrc[] =
+                "#extension GL_OES_EGL_image_external : require\n"
+                "precision mediump float;\n"
+                "uniform samplerExternalOES texSampler;\n"
+                "varying vec2 texCoords;\n"
+                "void main() {\n"
+                "  gl_FragColor = texture2D(texSampler, texCoords);\n"
+                "}\n";
+
+            {
+                SCOPED_TRACE("creating shader program");
+                ASSERT_NO_FATAL_FAILURE(createProgram(vsrc, fsrc, &mPgm));
+            }
 
-        GLfloat texMatrix[16];
-        mST->getTransformMatrix(texMatrix);
-        glUniformMatrix4fv(mTexMatrixHandle, 1, GL_FALSE, texMatrix);
+            mPositionHandle = glGetAttribLocation(mPgm, "vPosition");
+            ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+            ASSERT_NE(-1, mPositionHandle);
+            mTexSamplerHandle = glGetUniformLocation(mPgm, "texSampler");
+            ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+            ASSERT_NE(-1, mTexSamplerHandle);
+            mTexMatrixHandle = glGetUniformLocation(mPgm, "texMatrix");
+            ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+            ASSERT_NE(-1, mTexMatrixHandle);
+        }
 
-        glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
-        ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
-    }
+        // drawTexture draws the SurfaceTexture over the entire GL viewport.
+        void drawTexture() {
+            const GLfloat triangleVertices[] = {
+                -1.0f, 1.0f,
+                -1.0f, -1.0f,
+                1.0f, -1.0f,
+                1.0f, 1.0f,
+            };
+
+            glVertexAttribPointer(mPositionHandle, 2, GL_FLOAT, GL_FALSE, 0,
+                    triangleVertices);
+            ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+            glEnableVertexAttribArray(mPositionHandle);
+            ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+
+            glUseProgram(mPgm);
+            glUniform1i(mTexSamplerHandle, 0);
+            ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+            glBindTexture(GL_TEXTURE_EXTERNAL_OES, mTexName);
+            ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+
+            // XXX: These calls are not needed for GL_TEXTURE_EXTERNAL_OES as
+            // they're setting the defautls for that target, but when hacking
+            // things to use GL_TEXTURE_2D they are needed to achieve the same
+            // behavior.
+            glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER,
+                    GL_LINEAR);
+            ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+            glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER,
+                    GL_LINEAR);
+            ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+            glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S,
+                    GL_CLAMP_TO_EDGE);
+            ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+            glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T,
+                    GL_CLAMP_TO_EDGE);
+            ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+
+            GLfloat texMatrix[16];
+            mST->getTransformMatrix(texMatrix);
+            glUniformMatrix4fv(mTexMatrixHandle, 1, GL_FALSE, texMatrix);
+
+            glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
+            ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+        }
+
+        GLuint mTexName;
+        sp<SurfaceTexture> mST;
+        GLuint mPgm;
+        GLint mPositionHandle;
+        GLint mTexSamplerHandle;
+        GLint mTexMatrixHandle;
+    };
 
     class FrameWaiter : public SurfaceTexture::FrameAvailableListener {
     public:
@@ -470,11 +489,7 @@ protected:
     sp<SurfaceTexture> mST;
     sp<SurfaceTextureClient> mSTC;
     sp<ANativeWindow> mANW;
-
-    GLuint mPgm;
-    GLint mPositionHandle;
-    GLint mTexSamplerHandle;
-    GLint mTexMatrixHandle;
+    sp<TextureRenderer> mTextureRenderer;
 };
 
 // Fill a YV12 buffer with a multi-colored checkerboard pattern
@@ -1735,6 +1750,9 @@ TEST_F(SurfaceTextureFBOTest, BlitFromCpuFilledBufferToFbo) {
 
 class SurfaceTextureMultiContextGLTest : public SurfaceTextureGLTest {
 protected:
+    enum { SECOND_TEX_ID = 123 };
+    enum { THIRD_TEX_ID = 456 };
+
     SurfaceTextureMultiContextGLTest():
             mSecondEglContext(EGL_NO_CONTEXT) {
     }
@@ -1742,13 +1760,39 @@ protected:
     virtual void SetUp() {
         SurfaceTextureGLTest::SetUp();
 
+        // Set up the secondary context and texture renderer.
         mSecondEglContext = eglCreateContext(mEglDisplay, mGlConfig,
                 EGL_NO_CONTEXT, getContextAttribs());
         ASSERT_EQ(EGL_SUCCESS, eglGetError());
         ASSERT_NE(EGL_NO_CONTEXT, mSecondEglContext);
+
+        ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
+                mSecondEglContext));
+        ASSERT_EQ(EGL_SUCCESS, eglGetError());
+        mSecondTextureRenderer = new TextureRenderer(SECOND_TEX_ID, mST);
+        ASSERT_NO_FATAL_FAILURE(mSecondTextureRenderer->SetUp());
+
+        // Set up the tertiary context and texture renderer.
+        mThirdEglContext = eglCreateContext(mEglDisplay, mGlConfig,
+                EGL_NO_CONTEXT, getContextAttribs());
+        ASSERT_EQ(EGL_SUCCESS, eglGetError());
+        ASSERT_NE(EGL_NO_CONTEXT, mThirdEglContext);
+
+        ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
+                mThirdEglContext));
+        ASSERT_EQ(EGL_SUCCESS, eglGetError());
+        mThirdTextureRenderer = new TextureRenderer(THIRD_TEX_ID, mST);
+        ASSERT_NO_FATAL_FAILURE(mThirdTextureRenderer->SetUp());
+
+        // Switch back to the primary context to start the tests.
+        ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
+                mEglContext));
     }
 
     virtual void TearDown() {
+        if (mThirdEglContext != EGL_NO_CONTEXT) {
+            eglDestroyContext(mEglDisplay, mThirdEglContext);
+        }
         if (mSecondEglContext != EGL_NO_CONTEXT) {
             eglDestroyContext(mEglDisplay, mSecondEglContext);
         }
@@ -1756,6 +1800,10 @@ protected:
     }
 
     EGLContext mSecondEglContext;
+    sp<TextureRenderer> mSecondTextureRenderer;
+
+    EGLContext mThirdEglContext;
+    sp<TextureRenderer> mThirdTextureRenderer;
 };
 
 TEST_F(SurfaceTextureMultiContextGLTest, UpdateFromMultipleContextsFails) {
@@ -1765,13 +1813,382 @@ TEST_F(SurfaceTextureMultiContextGLTest, UpdateFromMultipleContextsFails) {
     ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
 
     // Latch the texture contents on the primary context.
-    mST->updateTexImage();
+    fw->waitForFrame();
+    ASSERT_EQ(OK, mST->updateTexImage());
 
     // Attempt to latch the texture on the secondary context.
-    EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
+    ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
+            mSecondEglContext));
+    ASSERT_EQ(EGL_SUCCESS, eglGetError());
+    ASSERT_EQ(INVALID_OPERATION, mST->updateTexImage());
+}
+
+TEST_F(SurfaceTextureMultiContextGLTest, DetachFromContextSucceeds) {
+    sp<FrameWaiter> fw(new FrameWaiter);
+    mST->setFrameAvailableListener(fw);
+
+    ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
+
+    // Latch the texture contents on the primary context.
+    fw->waitForFrame();
+    ASSERT_EQ(OK, mST->updateTexImage());
+
+    // Detach from the primary context.
+    ASSERT_EQ(OK, mST->detachFromContext());
+
+    // Check that the GL texture was deleted.
+    EXPECT_EQ(GL_FALSE, glIsTexture(TEX_ID));
+}
+
+TEST_F(SurfaceTextureMultiContextGLTest,
+        DetachFromContextSucceedsAfterProducerDisconnect) {
+    sp<FrameWaiter> fw(new FrameWaiter);
+    mST->setFrameAvailableListener(fw);
+
+    ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
+
+    // Latch the texture contents on the primary context.
+    fw->waitForFrame();
+    ASSERT_EQ(OK, mST->updateTexImage());
+
+    // Detach from the primary context.
+    native_window_api_disconnect(mANW.get(), NATIVE_WINDOW_API_CPU);
+    ASSERT_EQ(OK, mST->detachFromContext());
+
+    // Check that the GL texture was deleted.
+    EXPECT_EQ(GL_FALSE, glIsTexture(TEX_ID));
+}
+
+TEST_F(SurfaceTextureMultiContextGLTest, DetachFromContextFailsWhenAbandoned) {
+    sp<FrameWaiter> fw(new FrameWaiter);
+    mST->setFrameAvailableListener(fw);
+
+    ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
+
+    // Latch the texture contents on the primary context.
+    fw->waitForFrame();
+    ASSERT_EQ(OK, mST->updateTexImage());
+
+    // Attempt to detach from the primary context.
+    mST->abandon();
+    ASSERT_EQ(NO_INIT, mST->detachFromContext());
+}
+
+TEST_F(SurfaceTextureMultiContextGLTest, DetachFromContextFailsWhenDetached) {
+    sp<FrameWaiter> fw(new FrameWaiter);
+    mST->setFrameAvailableListener(fw);
+
+    ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
+
+    // Latch the texture contents on the primary context.
+    fw->waitForFrame();
+    ASSERT_EQ(OK, mST->updateTexImage());
+
+    // Detach from the primary context.
+    ASSERT_EQ(OK, mST->detachFromContext());
+
+    // Attempt to detach from the primary context again.
+    ASSERT_EQ(INVALID_OPERATION, mST->detachFromContext());
+}
+
+TEST_F(SurfaceTextureMultiContextGLTest, DetachFromContextFailsWithNoDisplay) {
+    sp<FrameWaiter> fw(new FrameWaiter);
+    mST->setFrameAvailableListener(fw);
+
+    ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
+
+    // Latch the texture contents on the primary context.
+    fw->waitForFrame();
+    ASSERT_EQ(OK, mST->updateTexImage());
+
+    // Make there be no current display.
+    ASSERT_TRUE(eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE,
+            EGL_NO_CONTEXT));
+    ASSERT_EQ(EGL_SUCCESS, eglGetError());
+
+    // Attempt to detach from the primary context.
+    ASSERT_EQ(INVALID_OPERATION, mST->detachFromContext());
+}
+
+TEST_F(SurfaceTextureMultiContextGLTest, DetachFromContextFailsWithNoContext) {
+    sp<FrameWaiter> fw(new FrameWaiter);
+    mST->setFrameAvailableListener(fw);
+
+    ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
+
+    // Latch the texture contents on the primary context.
+    fw->waitForFrame();
+    ASSERT_EQ(OK, mST->updateTexImage());
+
+    // Make current context be incorrect.
+    ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
             mSecondEglContext));
     ASSERT_EQ(EGL_SUCCESS, eglGetError());
-    ASSERT_EQ(-EINVAL, mST->updateTexImage());
+
+    // Attempt to detach from the primary context.
+    ASSERT_EQ(INVALID_OPERATION, mST->detachFromContext());
+}
+
+TEST_F(SurfaceTextureMultiContextGLTest, UpdateTexImageFailsWhenDetached) {
+    sp<FrameWaiter> fw(new FrameWaiter);
+    mST->setFrameAvailableListener(fw);
+
+    ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
+
+    // Detach from the primary context.
+    ASSERT_EQ(OK, mST->detachFromContext());
+
+    // Attempt to latch the texture contents on the primary context.
+    fw->waitForFrame();
+    ASSERT_EQ(INVALID_OPERATION, mST->updateTexImage());
+}
+
+TEST_F(SurfaceTextureMultiContextGLTest, AttachToContextSucceeds) {
+    sp<FrameWaiter> fw(new FrameWaiter);
+    mST->setFrameAvailableListener(fw);
+
+    ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
+
+    // Latch the texture contents on the primary context.
+    fw->waitForFrame();
+    ASSERT_EQ(OK, mST->updateTexImage());
+
+    // Detach from the primary context.
+    ASSERT_EQ(OK, mST->detachFromContext());
+
+    // Attach to the secondary context.
+    ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
+            mSecondEglContext));
+    ASSERT_EQ(OK, mST->attachToContext(SECOND_TEX_ID));
+
+    // Verify that the texture object was created and bound.
+    GLint texBinding = -1;
+    glGetIntegerv(GL_TEXTURE_BINDING_EXTERNAL_OES, &texBinding);
+    EXPECT_EQ(SECOND_TEX_ID, texBinding);
+
+    // Try to use the texture from the secondary context.
+    glClearColor(0.2, 0.2, 0.2, 0.2);
+    glClear(GL_COLOR_BUFFER_BIT);
+    glViewport(0, 0, 1, 1);
+    mSecondTextureRenderer->drawTexture();
+    ASSERT_TRUE(checkPixel( 0,  0,  35,  35,  35,  35));
+    ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+}
+
+TEST_F(SurfaceTextureMultiContextGLTest,
+        AttachToContextSucceedsAfterProducerDisconnect) {
+    sp<FrameWaiter> fw(new FrameWaiter);
+    mST->setFrameAvailableListener(fw);
+
+    ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
+
+    // Latch the texture contents on the primary context.
+    fw->waitForFrame();
+    ASSERT_EQ(OK, mST->updateTexImage());
+
+    // Detach from the primary context.
+    native_window_api_disconnect(mANW.get(), NATIVE_WINDOW_API_CPU);
+    ASSERT_EQ(OK, mST->detachFromContext());
+
+    // Attach to the secondary context.
+    ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
+            mSecondEglContext));
+    ASSERT_EQ(OK, mST->attachToContext(SECOND_TEX_ID));
+
+    // Verify that the texture object was created and bound.
+    GLint texBinding = -1;
+    glGetIntegerv(GL_TEXTURE_BINDING_EXTERNAL_OES, &texBinding);
+    EXPECT_EQ(SECOND_TEX_ID, texBinding);
+
+    // Try to use the texture from the secondary context.
+    glClearColor(0.2, 0.2, 0.2, 0.2);
+    glClear(GL_COLOR_BUFFER_BIT);
+    glViewport(0, 0, 1, 1);
+    mSecondTextureRenderer->drawTexture();
+    ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+    ASSERT_TRUE(checkPixel( 0,  0,  35,  35,  35,  35));
+}
+
+TEST_F(SurfaceTextureMultiContextGLTest,
+        AttachToContextSucceedsBeforeUpdateTexImage) {
+    sp<FrameWaiter> fw(new FrameWaiter);
+    mST->setFrameAvailableListener(fw);
+
+    ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
+
+    // Detach from the primary context.
+    native_window_api_disconnect(mANW.get(), NATIVE_WINDOW_API_CPU);
+    ASSERT_EQ(OK, mST->detachFromContext());
+
+    // Attach to the secondary context.
+    ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
+            mSecondEglContext));
+    ASSERT_EQ(OK, mST->attachToContext(SECOND_TEX_ID));
+
+    // Verify that the texture object was created and bound.
+    GLint texBinding = -1;
+    glGetIntegerv(GL_TEXTURE_BINDING_EXTERNAL_OES, &texBinding);
+    EXPECT_EQ(SECOND_TEX_ID, texBinding);
+
+    // Latch the texture contents on the primary context.
+    fw->waitForFrame();
+    ASSERT_EQ(OK, mST->updateTexImage());
+
+    // Try to use the texture from the secondary context.
+    glClearColor(0.2, 0.2, 0.2, 0.2);
+    glClear(GL_COLOR_BUFFER_BIT);
+    glViewport(0, 0, 1, 1);
+    mSecondTextureRenderer->drawTexture();
+    ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+    ASSERT_TRUE(checkPixel( 0,  0,  35,  35,  35,  35));
+}
+
+TEST_F(SurfaceTextureMultiContextGLTest, AttachToContextFailsWhenAbandoned) {
+    sp<FrameWaiter> fw(new FrameWaiter);
+    mST->setFrameAvailableListener(fw);
+
+    ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
+
+    // Latch the texture contents on the primary context.
+    fw->waitForFrame();
+    ASSERT_EQ(OK, mST->updateTexImage());
+
+    // Detach from the primary context.
+    ASSERT_EQ(OK, mST->detachFromContext());
+
+    // Attempt to attach to the secondary context.
+    mST->abandon();
+
+    // Attempt to attach to the primary context.
+    ASSERT_EQ(NO_INIT, mST->attachToContext(SECOND_TEX_ID));
+}
+
+TEST_F(SurfaceTextureMultiContextGLTest, AttachToContextFailsWhenAttached) {
+    sp<FrameWaiter> fw(new FrameWaiter);
+    mST->setFrameAvailableListener(fw);
+
+    ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
+
+    // Latch the texture contents on the primary context.
+    fw->waitForFrame();
+    ASSERT_EQ(OK, mST->updateTexImage());
+
+    // Attempt to attach to the primary context.
+    ASSERT_EQ(INVALID_OPERATION, mST->attachToContext(SECOND_TEX_ID));
+}
+
+TEST_F(SurfaceTextureMultiContextGLTest,
+        AttachToContextFailsWhenAttachedBeforeUpdateTexImage) {
+    sp<FrameWaiter> fw(new FrameWaiter);
+    mST->setFrameAvailableListener(fw);
+
+    ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
+
+    // Attempt to attach to the primary context.
+    ASSERT_EQ(INVALID_OPERATION, mST->attachToContext(SECOND_TEX_ID));
+}
+
+TEST_F(SurfaceTextureMultiContextGLTest, AttachToContextFailsWithNoDisplay) {
+    sp<FrameWaiter> fw(new FrameWaiter);
+    mST->setFrameAvailableListener(fw);
+
+    ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
+
+    // Latch the texture contents on the primary context.
+    fw->waitForFrame();
+    ASSERT_EQ(OK, mST->updateTexImage());
+
+    // Detach from the primary context.
+    ASSERT_EQ(OK, mST->detachFromContext());
+
+    // Make there be no current display.
+    ASSERT_TRUE(eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE,
+            EGL_NO_CONTEXT));
+    ASSERT_EQ(EGL_SUCCESS, eglGetError());
+
+    // Attempt to attach with no context current.
+    ASSERT_EQ(INVALID_OPERATION, mST->attachToContext(SECOND_TEX_ID));
+}
+
+TEST_F(SurfaceTextureMultiContextGLTest, AttachToContextSucceedsTwice) {
+    sp<FrameWaiter> fw(new FrameWaiter);
+    mST->setFrameAvailableListener(fw);
+
+    ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
+
+    // Latch the texture contents on the primary context.
+    fw->waitForFrame();
+    ASSERT_EQ(OK, mST->updateTexImage());
+
+    // Detach from the primary context.
+    ASSERT_EQ(OK, mST->detachFromContext());
+
+    // Attach to the secondary context.
+    ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
+            mSecondEglContext));
+    ASSERT_EQ(OK, mST->attachToContext(SECOND_TEX_ID));
+
+    // Detach from the secondary context.
+    ASSERT_EQ(OK, mST->detachFromContext());
+
+    // Attach to the tertiary context.
+    ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
+            mThirdEglContext));
+    ASSERT_EQ(OK, mST->attachToContext(THIRD_TEX_ID));
+
+    // Verify that the texture object was created and bound.
+    GLint texBinding = -1;
+    glGetIntegerv(GL_TEXTURE_BINDING_EXTERNAL_OES, &texBinding);
+    EXPECT_EQ(THIRD_TEX_ID, texBinding);
+
+    // Try to use the texture from the tertiary context.
+    glClearColor(0.2, 0.2, 0.2, 0.2);
+    glClear(GL_COLOR_BUFFER_BIT);
+    glViewport(0, 0, 1, 1);
+    mThirdTextureRenderer->drawTexture();
+    ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+    ASSERT_TRUE(checkPixel( 0,  0,  35,  35,  35,  35));
+}
+
+TEST_F(SurfaceTextureMultiContextGLTest,
+        AttachToContextSucceedsTwiceBeforeUpdateTexImage) {
+    sp<FrameWaiter> fw(new FrameWaiter);
+    mST->setFrameAvailableListener(fw);
+
+    ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
+
+    // Detach from the primary context.
+    ASSERT_EQ(OK, mST->detachFromContext());
+
+    // Attach to the secondary context.
+    ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
+            mSecondEglContext));
+    ASSERT_EQ(OK, mST->attachToContext(SECOND_TEX_ID));
+
+    // Detach from the secondary context.
+    ASSERT_EQ(OK, mST->detachFromContext());
+
+    // Attach to the tertiary context.
+    ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
+            mThirdEglContext));
+    ASSERT_EQ(OK, mST->attachToContext(THIRD_TEX_ID));
+
+    // Verify that the texture object was created and bound.
+    GLint texBinding = -1;
+    glGetIntegerv(GL_TEXTURE_BINDING_EXTERNAL_OES, &texBinding);
+    EXPECT_EQ(THIRD_TEX_ID, texBinding);
+
+    // Latch the texture contents on the tertiary context.
+    fw->waitForFrame();
+    ASSERT_EQ(OK, mST->updateTexImage());
+
+    // Try to use the texture from the tertiary context.
+    glClearColor(0.2, 0.2, 0.2, 0.2);
+    glClear(GL_COLOR_BUFFER_BIT);
+    glViewport(0, 0, 1, 1);
+    mThirdTextureRenderer->drawTexture();
+    ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+    ASSERT_TRUE(checkPixel( 0,  0,  35,  35,  35,  35));
 }
 
 } // namespace android