Implement SurfaceTexture frame-available callback.

This change implements the onFrameAvailable callback for the
SurfaceTexture java class.  It includes the C++ SurfaceTexture code as
well as the JNI and Java code to enable the callback.

Change-Id: Ifd8b8e7ad46ee70cba6da1c2e96dab8045d1ea30
diff --git a/core/jni/android/graphics/SurfaceTexture.cpp b/core/jni/android/graphics/SurfaceTexture.cpp
index c1229f3..88de94f 100644
--- a/core/jni/android/graphics/SurfaceTexture.cpp
+++ b/core/jni/android/graphics/SurfaceTexture.cpp
@@ -25,8 +25,8 @@
 #include <utils/Log.h>
 #include <utils/misc.h>
 
-#include "android/graphics/GraphicsJNI.h"
 #include "jni.h"
+#include "JNIHelp.h"
 
 // ----------------------------------------------------------------------------
 
@@ -35,57 +35,127 @@
 static const char* const OutOfResourcesException =
     "android/graphics/SurfaceTexture$OutOfResourcesException";
 
-struct st_t {
-    jfieldID surfaceTexture;
+struct fields_t {
+    jfieldID  surfaceTexture;
+    jmethodID postEvent;
 };
-static st_t st;
+static fields_t fields;
 
 // ----------------------------------------------------------------------------
 
-static void setSurfaceTexture(JNIEnv* env, jobject clazz,
+static void SurfaceTexture_setSurfaceTexture(JNIEnv* env, jobject thiz,
         const sp<SurfaceTexture>& surfaceTexture)
 {
     SurfaceTexture* const p =
-        (SurfaceTexture*)env->GetIntField(clazz, st.surfaceTexture);
+        (SurfaceTexture*)env->GetIntField(thiz, fields.surfaceTexture);
     if (surfaceTexture.get()) {
-        surfaceTexture->incStrong(clazz);
+        surfaceTexture->incStrong(thiz);
     }
     if (p) {
-        p->decStrong(clazz);
+        p->decStrong(thiz);
     }
-    env->SetIntField(clazz, st.surfaceTexture, (int)surfaceTexture.get());
+    env->SetIntField(thiz, fields.surfaceTexture, (int)surfaceTexture.get());
 }
 
-sp<SurfaceTexture> getSurfaceTexture(JNIEnv* env, jobject clazz)
+sp<SurfaceTexture> SurfaceTexture_getSurfaceTexture(JNIEnv* env, jobject thiz)
 {
     sp<SurfaceTexture> surfaceTexture(
-        (SurfaceTexture*)env->GetIntField(clazz, st.surfaceTexture));
+        (SurfaceTexture*)env->GetIntField(thiz, fields.surfaceTexture));
     return surfaceTexture;
 }
 
 // ----------------------------------------------------------------------------
 
-static void SurfaceTexture_init(JNIEnv* env, jobject clazz, jint texName)
+class JNISurfaceTextureContext : public SurfaceTexture::FrameAvailableListener
 {
-    sp<SurfaceTexture> surfaceTexture(new SurfaceTexture(texName));
+public:
+    JNISurfaceTextureContext(JNIEnv* env, jobject weakThiz, jclass clazz);
+    virtual ~JNISurfaceTextureContext();
+    virtual void onFrameAvailable();
 
-    if (surfaceTexture == 0) {
-        doThrow(env, OutOfResourcesException);
-        return;
-    }
-    setSurfaceTexture(env, clazz, surfaceTexture);
+private:
+    jobject mWeakThiz;
+    jclass mClazz;
+};
+
+JNISurfaceTextureContext::JNISurfaceTextureContext(JNIEnv* env,
+        jobject weakThiz, jclass clazz) :
+    mWeakThiz(env->NewGlobalRef(weakThiz)),
+    mClazz((jclass)env->NewGlobalRef(clazz))
+{}
+
+JNISurfaceTextureContext::~JNISurfaceTextureContext()
+{
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    env->DeleteGlobalRef(mWeakThiz);
+    env->DeleteGlobalRef(mClazz);
 }
 
-static void SurfaceTexture_updateTexImage(JNIEnv* env, jobject clazz)
+void JNISurfaceTextureContext::onFrameAvailable()
 {
-    sp<SurfaceTexture> surfaceTexture(getSurfaceTexture(env, clazz));
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    env->CallStaticVoidMethod(mClazz, fields.postEvent, mWeakThiz);
+}
+
+// ----------------------------------------------------------------------------
+
+static void SurfaceTexture_classInit(JNIEnv* env, jclass clazz)
+{
+    fields.surfaceTexture = env->GetFieldID(clazz,
+            ANDROID_GRAPHICS_SURFACETEXTURE_JNI_ID, "I");
+    if (fields.surfaceTexture == NULL) {
+        LOGE("can't find android/graphics/SurfaceTexture.%s",
+                ANDROID_GRAPHICS_SURFACETEXTURE_JNI_ID);
+    }
+
+    fields.postEvent = env->GetStaticMethodID(clazz, "postEventFromNative",
+            "(Ljava/lang/Object;)V");
+    if (fields.postEvent == NULL) {
+        LOGE("can't find android/graphics/SurfaceTexture.postEventFromNative");
+    }
+
+}
+
+static void SurfaceTexture_init(JNIEnv* env, jobject thiz, jint texName,
+        jobject weakThiz)
+{
+    sp<SurfaceTexture> surfaceTexture(new SurfaceTexture(texName));
+    if (surfaceTexture == 0) {
+        jniThrowException(env, OutOfResourcesException,
+                "Unable to create native SurfaceTexture");
+        return;
+    }
+    SurfaceTexture_setSurfaceTexture(env, thiz, surfaceTexture);
+
+    jclass clazz = env->GetObjectClass(thiz);
+    if (clazz == NULL) {
+        jniThrowRuntimeException(env,
+                "Can't find android/graphics/SurfaceTexture");
+        return;
+    }
+
+    sp<JNISurfaceTextureContext> ctx(new JNISurfaceTextureContext(env, weakThiz,
+            clazz));
+    surfaceTexture->setFrameAvailableListener(ctx);
+}
+
+static void SurfaceTexture_finalize(JNIEnv* env, jobject thiz)
+{
+    sp<SurfaceTexture> surfaceTexture(SurfaceTexture_getSurfaceTexture(env, thiz));
+    surfaceTexture->setFrameAvailableListener(0);
+    SurfaceTexture_setSurfaceTexture(env, thiz, 0);
+}
+
+static void SurfaceTexture_updateTexImage(JNIEnv* env, jobject thiz)
+{
+    sp<SurfaceTexture> surfaceTexture(SurfaceTexture_getSurfaceTexture(env, thiz));
     surfaceTexture->updateTexImage();
 }
 
-static void SurfaceTexture_getTransformMatrix(JNIEnv* env, jobject clazz,
+static void SurfaceTexture_getTransformMatrix(JNIEnv* env, jobject thiz,
         jfloatArray jmtx)
 {
-    sp<SurfaceTexture> surfaceTexture(getSurfaceTexture(env, clazz));
+    sp<SurfaceTexture> surfaceTexture(SurfaceTexture_getSurfaceTexture(env, thiz));
     float* mtx = env->GetFloatArrayElements(jmtx, NULL);
     surfaceTexture->getTransformMatrix(mtx);
     env->ReleaseFloatArrayElements(jmtx, mtx, 0);
@@ -94,21 +164,15 @@
 // ----------------------------------------------------------------------------
 
 const char* const kSurfaceTextureClassPathName = "android/graphics/SurfaceTexture";
-static void nativeClassInit(JNIEnv* env, jclass clazz);
 
 static JNINativeMethod gSurfaceTextureMethods[] = {
-    {"nativeClassInit",     "()V",  (void*)nativeClassInit },
-    {"init",                "(I)V", (void*)SurfaceTexture_init },
-    {"updateTexImage",      "()V",  (void*)SurfaceTexture_updateTexImage },
-    {"getTransformMatrixImpl", "([F)V",  (void*)SurfaceTexture_getTransformMatrix },
+    {"nativeClassInit",          "()V",   (void*)SurfaceTexture_classInit },
+    {"nativeInit",               "(ILjava/lang/Object;)V", (void*)SurfaceTexture_init },
+    {"nativeFinalize",            "()V",  (void*)SurfaceTexture_finalize },
+    {"nativeUpdateTexImage",     "()V",   (void*)SurfaceTexture_updateTexImage },
+    {"nativeGetTransformMatrix", "([F)V", (void*)SurfaceTexture_getTransformMatrix },
 };
 
-static void nativeClassInit(JNIEnv* env, jclass clazz)
-{
-    st.surfaceTexture = env->GetFieldID(clazz,
-            ANDROID_GRAPHICS_SURFACETEXTURE_JNI_ID, "I");
-}
-
 int register_android_graphics_SurfaceTexture(JNIEnv* env)
 {
     int err = 0;
diff --git a/graphics/java/android/graphics/SurfaceTexture.java b/graphics/java/android/graphics/SurfaceTexture.java
index 3eb0b03..64c209a 100644
--- a/graphics/java/android/graphics/SurfaceTexture.java
+++ b/graphics/java/android/graphics/SurfaceTexture.java
@@ -16,6 +16,11 @@
 
 package android.graphics;
 
+import java.lang.ref.WeakReference;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
 /**
  * Captures frames from an image stream as an OpenGL ES texture.
  *
@@ -32,6 +37,9 @@
  */
 public class SurfaceTexture {
 
+    private EventHandler mEventHandler;
+    private OnFrameAvailableListener mOnFrameAvailableListener;
+
     @SuppressWarnings("unused")
     private int mSurfaceTexture;
 
@@ -59,7 +67,15 @@
      * @param texName the OpenGL texture object name (e.g. generated via glGenTextures)
      */
     public SurfaceTexture(int texName) {
-        init(texName);
+        Looper looper;
+        if ((looper = Looper.myLooper()) != null) {
+            mEventHandler = new EventHandler(looper);
+        } else if ((looper = Looper.getMainLooper()) != null) {
+            mEventHandler = new EventHandler(looper);
+        } else {
+            mEventHandler = null;
+        }
+        nativeInit(texName, new WeakReference<SurfaceTexture>(this));
     }
 
     /**
@@ -69,7 +85,7 @@
      * thread invoking the callback.
      */
     public void setOnFrameAvailableListener(OnFrameAvailableListener l) {
-        // TODO: Implement this!
+        mOnFrameAvailableListener = l;
     }
 
     /**
@@ -77,8 +93,9 @@
      * called while the OpenGL ES context that owns the texture is bound to the thread.  It will
      * implicitly bind its texture to the GL_TEXTURE_EXTERNAL_OES texture target.
      */
-    public native void updateTexImage();
-
+    public void updateTexImage() {
+        nativeUpdateTexImage();
+    }
 
     /**
      * Retrieve the 4x4 texture coordinate transform matrix associated with the texture image set by
@@ -99,12 +116,48 @@
         if (mtx.length != 16) {
             throw new IllegalArgumentException();
         }
-        getTransformMatrixImpl(mtx);
+        nativeGetTransformMatrix(mtx);
     }
 
-    private native void getTransformMatrixImpl(float[] mtx);
+    protected void finalize() throws Throwable {
+        try {
+            nativeFinalize();
+        } finally {
+            super.finalize();
+        }
+    }
 
-    private native void init(int texName);
+    private class EventHandler extends Handler {
+        public EventHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (mOnFrameAvailableListener != null) {
+                mOnFrameAvailableListener.onFrameAvailable(SurfaceTexture.this);
+            }
+            return;
+        }
+    }
+
+    private static void postEventFromNative(Object selfRef) {
+        WeakReference weakSelf = (WeakReference)selfRef;
+        SurfaceTexture st = (SurfaceTexture)weakSelf.get();
+        if (st == null) {
+            return;
+        }
+
+        if (st.mEventHandler != null) {
+            Message m = st.mEventHandler.obtainMessage();
+            st.mEventHandler.sendMessage(m);
+        }
+    }
+
+    private native void nativeInit(int texName, Object weakSelf);
+    private native void nativeFinalize();
+    private native void nativeGetTransformMatrix(float[] mtx);
+    private native void nativeUpdateTexImage();
 
     /*
      * We use a class initializer to allow the native code to cache some
diff --git a/include/gui/SurfaceTexture.h b/include/gui/SurfaceTexture.h
index 002e48b..79c33f5 100644
--- a/include/gui/SurfaceTexture.h
+++ b/include/gui/SurfaceTexture.h
@@ -40,6 +40,10 @@
     enum { MIN_BUFFER_SLOTS = 3 };
     enum { NUM_BUFFER_SLOTS = 32 };
 
+    struct FrameAvailableListener : public virtual RefBase {
+        virtual void onFrameAvailable() = 0;
+    };
+
     // tex indicates the name OpenGL texture to which images are to be streamed.
     // This texture name cannot be changed once the SurfaceTexture is created.
     SurfaceTexture(GLuint tex);
@@ -93,6 +97,10 @@
     // functions.
     void getTransformMatrix(float mtx[16]);
 
+    // setFrameAvailableListener sets the listener object that will be notified
+    // when a new frame becomes available.
+    void setFrameAvailableListener(const sp<FrameAvailableListener>& l);
+
 private:
 
     // freeAllBuffers frees the resources (both GraphicBuffer and EGLImage) for
@@ -195,6 +203,11 @@
     // to a buffer, but other processes do.
     Vector<sp<GraphicBuffer> > mAllocdBuffers;
 
+    // mFrameAvailableListener is the listener object that will be called when a
+    // new frame becomes available. If it is not NULL it will be called from
+    // queueBuffer.
+    sp<FrameAvailableListener> mFrameAvailableListener;
+
     // 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.
diff --git a/libs/gui/SurfaceTexture.cpp b/libs/gui/SurfaceTexture.cpp
index 88f1728..1dadd53 100644
--- a/libs/gui/SurfaceTexture.cpp
+++ b/libs/gui/SurfaceTexture.cpp
@@ -166,6 +166,9 @@
     mLastQueued = buf;
     mLastQueuedCrop = mNextCrop;
     mLastQueuedTransform = mNextTransform;
+    if (mFrameAvailableListener != 0) {
+        mFrameAvailableListener->onFrameAvailable();
+    }
     return OK;
 }
 
@@ -294,6 +297,13 @@
     mtxMul(mtx, mtxFlipV, mtxBeforeFlipV);
 }
 
+void SurfaceTexture::setFrameAvailableListener(
+        const sp<FrameAvailableListener>& l) {
+    LOGV("SurfaceTexture::setFrameAvailableListener");
+    Mutex::Autolock lock(mMutex);
+    mFrameAvailableListener = l;
+}
+
 void SurfaceTexture::freeAllBuffers() {
     for (int i = 0; i < NUM_BUFFER_SLOTS; i++) {
         mSlots[i].mGraphicBuffer = 0;