Fix crash due to texture view callback threading invariants.

Allow the client of a SurfaceTexture to specify the Handler to
which the update callback should be directed to avoid unnecessary
scheduling ping-pong between threads.

Fixed an invalid assumption in TextureView that it is attached
to the main looper which could result in a crash under certain
circumstances.  In normal app processes, it is true that TextureViews
must be created on the main looper since hardware rendering is
currently only supported on the main looper.  However, in the
system server, UI components run a different thread.  Although
hardware rendering is normally disabled in the system server,
it may be enabled for certain developer features.

Bug: 14445309
Change-Id: I5ae17ad018b9ef05ba87ec2f972c7c82e2bca70a
diff --git a/api/current.txt b/api/current.txt
index a7118960..d787c9a 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -10734,6 +10734,7 @@
     method public void releaseTexImage();
     method public void setDefaultBufferSize(int, int);
     method public void setOnFrameAvailableListener(android.graphics.SurfaceTexture.OnFrameAvailableListener);
+    method public void setOnFrameAvailableListener(android.graphics.SurfaceTexture.OnFrameAvailableListener, android.os.Handler);
     method public void updateTexImage();
   }
 
diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java
index 3cfe5e9..1765c43 100644
--- a/core/java/android/view/TextureView.java
+++ b/core/java/android/view/TextureView.java
@@ -23,7 +23,6 @@
 import android.graphics.Paint;
 import android.graphics.Rect;
 import android.graphics.SurfaceTexture;
-import android.os.Looper;
 import android.util.AttributeSet;
 import android.util.Log;
 
@@ -119,8 +118,6 @@
     private boolean mUpdateLayer;
     private boolean mUpdateSurface;
 
-    private SurfaceTexture.OnFrameAvailableListener mUpdateListener;
-
     private Canvas mCanvas;
     private int mSaveCount;
 
@@ -370,21 +367,7 @@
             mSurface.setDefaultBufferSize(getWidth(), getHeight());
             nCreateNativeWindow(mSurface);
 
-            mUpdateListener = new SurfaceTexture.OnFrameAvailableListener() {
-                @Override
-                public void onFrameAvailable(SurfaceTexture surfaceTexture) {
-                    // Per SurfaceTexture's documentation, the callback may be invoked
-                    // from an arbitrary thread
-                    updateLayer();
-
-                    if (Looper.myLooper() == Looper.getMainLooper()) {
-                        invalidate();
-                    } else {
-                        postInvalidate();
-                    }
-                }
-            };
-            mSurface.setOnFrameAvailableListener(mUpdateListener);
+            mSurface.setOnFrameAvailableListener(mUpdateListener, mAttachInfo.mHandler);
 
             if (mListener != null && !mUpdateSurface) {
                 mListener.onSurfaceTextureAvailable(mSurface, getWidth(), getHeight());
@@ -422,7 +405,7 @@
             // To cancel updates, the easiest thing to do is simply to remove the
             // updates listener
             if (visibility == VISIBLE) {
-                mSurface.setOnFrameAvailableListener(mUpdateListener);
+                mSurface.setOnFrameAvailableListener(mUpdateListener, mAttachInfo.mHandler);
                 updateLayerAndInvalidate();
             } else {
                 mSurface.setOnFrameAvailableListener(null);
@@ -767,6 +750,15 @@
         mListener = listener;
     }
 
+    private final SurfaceTexture.OnFrameAvailableListener mUpdateListener =
+            new SurfaceTexture.OnFrameAvailableListener() {
+        @Override
+        public void onFrameAvailable(SurfaceTexture surfaceTexture) {
+            updateLayer();
+            invalidate();
+        }
+    };
+
     /**
      * This listener can be used to be notified when the surface texture
      * associated with this texture view is available.
diff --git a/core/jni/android/graphics/SurfaceTexture.cpp b/core/jni/android/graphics/SurfaceTexture.cpp
index b78b131..621534e 100644
--- a/core/jni/android/graphics/SurfaceTexture.cpp
+++ b/core/jni/android/graphics/SurfaceTexture.cpp
@@ -221,7 +221,7 @@
     }
 
     fields.postEvent = env->GetStaticMethodID(clazz, "postEventFromNative",
-            "(Ljava/lang/Object;)V");
+            "(Ljava/lang/ref/WeakReference;)V");
     if (fields.postEvent == NULL) {
         ALOGE("can't find android/graphics/SurfaceTexture.postEventFromNative");
     }
@@ -338,7 +338,7 @@
 
 static JNINativeMethod gSurfaceTextureMethods[] = {
     {"nativeClassInit",            "()V",   (void*)SurfaceTexture_classInit },
-    {"nativeInit",                 "(IZLjava/lang/Object;)V", (void*)SurfaceTexture_init },
+    {"nativeInit",                 "(IZLjava/lang/ref/WeakReference;)V", (void*)SurfaceTexture_init },
     {"nativeFinalize",             "()V",   (void*)SurfaceTexture_finalize },
     {"nativeSetDefaultBufferSize", "(II)V", (void*)SurfaceTexture_setDefaultBufferSize },
     {"nativeUpdateTexImage",       "()V",   (void*)SurfaceTexture_updateTexImage },
diff --git a/graphics/java/android/graphics/SurfaceTexture.java b/graphics/java/android/graphics/SurfaceTexture.java
index d877502..3f8c45c 100644
--- a/graphics/java/android/graphics/SurfaceTexture.java
+++ b/graphics/java/android/graphics/SurfaceTexture.java
@@ -62,9 +62,8 @@
  * #updateTexImage} should not be called directly from the callback.
  */
 public class SurfaceTexture {
-
-    private EventHandler mEventHandler;
-    private OnFrameAvailableListener mOnFrameAvailableListener;
+    private final Looper mCreatorLooper;
+    private Handler mOnFrameAvailableHandler;
 
     /**
      * These fields are used by native code, do not access or modify.
@@ -83,7 +82,8 @@
     /**
      * Exception thrown when a SurfaceTexture couldn't be created or resized.
      *
-     * @deprecated No longer thrown. {@link Surface.OutOfResourcesException} is used instead.
+     * @deprecated No longer thrown. {@link android.view.Surface.OutOfResourcesException}
+     * is used instead.
      */
     @SuppressWarnings("serial")
     @Deprecated
@@ -100,10 +100,10 @@
      *
      * @param texName the OpenGL texture object name (e.g. generated via glGenTextures)
      *
-     * @throws OutOfResourcesException If the SurfaceTexture cannot be created.
+     * @throws Surface.OutOfResourcesException If the SurfaceTexture cannot be created.
      */
     public SurfaceTexture(int texName) {
-        init(texName, false);
+        this(texName, false);
     }
 
     /**
@@ -121,20 +121,58 @@
      * @param texName the OpenGL texture object name (e.g. generated via glGenTextures)
      * @param singleBufferMode whether the SurfaceTexture will be in single buffered mode.
      *
-     * @throws throws OutOfResourcesException If the SurfaceTexture cannot be created.
+     * @throws Surface.OutOfResourcesException If the SurfaceTexture cannot be created.
      */
     public SurfaceTexture(int texName, boolean singleBufferMode) {
-        init(texName, singleBufferMode);
+        mCreatorLooper = Looper.myLooper();
+        nativeInit(texName, singleBufferMode, new WeakReference<SurfaceTexture>(this));
     }
 
     /**
      * Register a callback to be invoked when a new image frame becomes available to the
-     * SurfaceTexture.  Note that this callback may be called on an arbitrary thread, so it is not
+     * SurfaceTexture.
+     * <p>
+     * This callback may be called on an arbitrary thread, so it is not
      * safe to call {@link #updateTexImage} without first binding the OpenGL ES context to the
      * thread invoking the callback.
+     * </p>
+     *
+     * @param listener The listener to set.
      */
-    public void setOnFrameAvailableListener(OnFrameAvailableListener l) {
-        mOnFrameAvailableListener = l;
+    public void setOnFrameAvailableListener(OnFrameAvailableListener listener) {
+        setOnFrameAvailableListener(listener, null);
+    }
+
+    /**
+     * Register a callback to be invoked when a new image frame becomes available to the
+     * SurfaceTexture.
+     * <p>
+     * If no handler is specified, then this callback may be called on an arbitrary thread,
+     * so it is not safe to call {@link #updateTexImage} without first binding the OpenGL ES
+     * context to the thread invoking the callback.
+     * </p>
+     *
+     * @param listener The listener to set.
+     * @param handler The handler on which the listener should be invoked, or null
+     * to use an arbitrary thread.
+     */
+    public void setOnFrameAvailableListener(final OnFrameAvailableListener listener,
+            Handler handler) {
+        if (listener != null) {
+            // Although we claim the thread is arbitrary, earlier implementation would
+            // prefer to send the callback on the creating looper or the main looper
+            // so we preserve this behavior here.
+            Looper looper = handler != null ? handler.getLooper() :
+                    mCreatorLooper != null ? mCreatorLooper : Looper.getMainLooper();
+            mOnFrameAvailableHandler = new Handler(looper, null, true /*async*/) {
+                @Override
+                public void handleMessage(Message msg) {
+                    listener.onFrameAvailable(SurfaceTexture.this);
+                }
+            };
+        } else {
+            mOnFrameAvailableHandler = null;
+        }
     }
 
     /**
@@ -285,49 +323,22 @@
         }
     }
 
-    private class EventHandler extends Handler {
-        public EventHandler(Looper looper) {
-            super(looper);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            if (mOnFrameAvailableListener != null) {
-                mOnFrameAvailableListener.onFrameAvailable(SurfaceTexture.this);
-            }
-        }
-    }
-
     /**
      * This method is invoked from native code only.
      */
     @SuppressWarnings({"UnusedDeclaration"})
-    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 static void postEventFromNative(WeakReference<SurfaceTexture> weakSelf) {
+        SurfaceTexture st = weakSelf.get();
+        if (st != null) {
+            Handler handler = st.mOnFrameAvailableHandler;
+            if (handler != null) {
+                handler.sendEmptyMessage(0);
+            }
         }
     }
 
-    private void init(int texName, boolean singleBufferMode) throws Surface.OutOfResourcesException {
-        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, singleBufferMode, new WeakReference<SurfaceTexture>(this));
-    }
-
-    private native void nativeInit(int texName, boolean singleBufferMode, Object weakSelf)
+    private native void nativeInit(int texName, boolean singleBufferMode,
+            WeakReference<SurfaceTexture> weakSelf)
             throws Surface.OutOfResourcesException;
     private native void nativeFinalize();
     private native void nativeGetTransformMatrix(float[] mtx);