New widget: TextureView
Bug #4343984

TextureView can be used to render media content (video, OpenGL,
RenderScript) inside a View.

The key difference with SurfaceView is that TextureView does
not create a new Surface. This gives the ability to seamlessly
transform, animate, fade, etc. a TextureView, which was hard
if not impossible to do with a SurfaceView.
A TextureView also interacts perfectly with ScrollView,
ListView, etc. It allows application to embed media content
in a much more flexible way than before.

For instance, to render the camera preview at 50% opacity,
all you need to do is the following:

mTextureView.setAlpha(0.5f);
Camera c = Camera.open();
c.setPreviewTexture(mTextureView.getSurfaceTexture());
c.startPreview();

TextureView uses a SurfaceTexture to get the job done. More
APIs are required to make it easy to create OpenGL contexts
for a TextureView. It can currently be done with a bit of
JNI code.

Change-Id: Iaa7953097ab5beb8437bcbbfa03b2df5b7f80cd7
diff --git a/api/current.txt b/api/current.txt
index ebf3893..d568254 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -20962,6 +20962,21 @@
     method public void setZOrderOnTop(boolean);
   }
 
+  public class TextureView extends android.view.View {
+    ctor public TextureView(android.content.Context);
+    ctor public TextureView(android.content.Context, android.util.AttributeSet);
+    ctor public TextureView(android.content.Context, android.util.AttributeSet, int);
+    method public final void draw(android.graphics.Canvas);
+    method public android.graphics.SurfaceTexture getSurfaceTexture();
+    method public android.view.TextureView.SurfaceTextureListener getSurfaceTextureListener();
+    method protected final void onDraw(android.graphics.Canvas);
+    method public void setSurfaceTextureListener(android.view.TextureView.SurfaceTextureListener);
+  }
+
+  public static abstract interface TextureView.SurfaceTextureListener {
+    method public abstract void onSurfaceTextureAvailable(android.graphics.SurfaceTexture);
+  }
+
   public class TouchDelegate {
     ctor public TouchDelegate(android.graphics.Rect, android.view.View);
     method public boolean onTouchEvent(android.view.MotionEvent);
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
index b8c5c2a..cdf8954 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/GLES20Canvas.java
@@ -160,8 +160,11 @@
     // Hardware layers
     ///////////////////////////////////////////////////////////////////////////
     
+    static native int nCreateTextureLayer(int[] layerInfo);
     static native int nCreateLayer(int width, int height, boolean isOpaque, int[] layerInfo);
     static native void nResizeLayer(int layerId, int width, int height, int[] layerInfo);
+    static native void nUpdateTextureLayer(int layerId, int width, int height,
+            float[] textureTransform);
     static native void nDestroyLayer(int layerId);
     static native void nDestroyLayerDeferred(int layerId);
 
@@ -272,7 +275,7 @@
     }
 
     private static native void nDrawLayer(int renderer, int layer, float x, float y, int paint);
-    
+
     void interrupt() {
         nInterrupt(mRenderer);
     }
diff --git a/core/java/android/view/GLES20Layer.java b/core/java/android/view/GLES20Layer.java
index 6000a4a..bc191a6 100644
--- a/core/java/android/view/GLES20Layer.java
+++ b/core/java/android/view/GLES20Layer.java
@@ -14,39 +14,21 @@
  * limitations under the License.
  */
 
-package android.view;
 
-import android.graphics.Canvas;
+package android.view;
 
 /**
  * An OpenGL ES 2.0 implementation of {@link HardwareLayer}.
  */
-class GLES20Layer extends HardwareLayer {
-    private int mLayer;
+abstract class GLES20Layer extends HardwareLayer {
+    int mLayer;
+    Finalizer mFinalizer;
 
-    private int mLayerWidth;
-    private int mLayerHeight;
+    GLES20Layer() {
+    }
 
-    private final GLES20Canvas mCanvas;
-
-    @SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"})
-    private final Finalizer mFinalizer;
-
-    GLES20Layer(int width, int height, boolean isOpaque) {
-        super(width, height, isOpaque);
-
-        int[] layerInfo = new int[2];
-        mLayer = GLES20Canvas.nCreateLayer(width, height, isOpaque, layerInfo);
-        if (mLayer != 0) {
-            mLayerWidth = layerInfo[0];
-            mLayerHeight = layerInfo[1];
-
-            mCanvas = new GLES20Canvas(mLayer, !isOpaque);
-            mFinalizer = new Finalizer(mLayer);
-        } else {
-            mCanvas = null;
-            mFinalizer = null;
-        }
+    GLES20Layer(int width, int height, boolean opaque) {
+        super(width, height, opaque);
     }
 
     /**
@@ -58,55 +40,14 @@
         return mLayer;
     }
 
-    @Override
-    boolean isValid() {
-        return mLayer != 0 && mLayerWidth > 0 && mLayerHeight > 0;
-    }
-
-    @Override
-    void resize(int width, int height) {
-        if (!isValid() || width <= 0 || height <= 0) return;
-
-        mWidth = width;
-        mHeight = height;
-        
-        if (width != mLayerWidth || height != mLayerHeight) {
-            int[] layerInfo = new int[2];
-
-            GLES20Canvas.nResizeLayer(mLayer, width, height, layerInfo);
-
-            mLayerWidth = layerInfo[0];
-            mLayerHeight = layerInfo[1];
-        }
-    }
-
-    @Override
-    HardwareCanvas getCanvas() {
-        return mCanvas;
-    }
-
-    @Override
-    void end(Canvas currentCanvas) {
-        if (currentCanvas instanceof GLES20Canvas) {
-            ((GLES20Canvas) currentCanvas).resume();
-        }
-    }
-
-    @Override
-    HardwareCanvas start(Canvas currentCanvas) {
-        if (currentCanvas instanceof GLES20Canvas) {
-            ((GLES20Canvas) currentCanvas).interrupt();
-        }
-        return getCanvas();
-    }
-
+    
     @Override
     void destroy() {
-        mFinalizer.destroy();
+        if (mFinalizer != null) mFinalizer.destroy();
         mLayer = 0;
     }
 
-    private static class Finalizer {
+    static class Finalizer {
         private int mLayerId;
 
         public Finalizer(int layerId) {
diff --git a/core/java/android/view/GLES20RenderLayer.java b/core/java/android/view/GLES20RenderLayer.java
new file mode 100644
index 0000000..7adac1c
--- /dev/null
+++ b/core/java/android/view/GLES20RenderLayer.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.graphics.Canvas;
+
+/**
+ * An OpenGL ES 2.0 implementation of {@link HardwareLayer}. This
+ * implementation can be used a rendering target. It generates a
+ * {@link Canvas} that can be used to render into an FBO using OpenGL.
+ */
+class GLES20RenderLayer extends GLES20Layer {
+
+    private int mLayerWidth;
+    private int mLayerHeight;
+
+    private final GLES20Canvas mCanvas;
+
+    GLES20RenderLayer(int width, int height, boolean isOpaque) {
+        super(width, height, isOpaque);
+
+        int[] layerInfo = new int[2];
+        mLayer = GLES20Canvas.nCreateLayer(width, height, isOpaque, layerInfo);
+        if (mLayer != 0) {
+            mLayerWidth = layerInfo[0];
+            mLayerHeight = layerInfo[1];
+
+            mCanvas = new GLES20Canvas(mLayer, !isOpaque);
+            mFinalizer = new Finalizer(mLayer);
+        } else {
+            mCanvas = null;
+            mFinalizer = null;
+        }
+    }
+
+    @Override
+    boolean isValid() {
+        return mLayer != 0 && mLayerWidth > 0 && mLayerHeight > 0;
+    }
+
+    @Override
+    void resize(int width, int height) {
+        if (!isValid() || width <= 0 || height <= 0) return;
+
+        mWidth = width;
+        mHeight = height;
+        
+        if (width != mLayerWidth || height != mLayerHeight) {
+            int[] layerInfo = new int[2];
+
+            GLES20Canvas.nResizeLayer(mLayer, width, height, layerInfo);
+
+            mLayerWidth = layerInfo[0];
+            mLayerHeight = layerInfo[1];
+        }
+    }
+
+    @Override
+    HardwareCanvas getCanvas() {
+        return mCanvas;
+    }
+
+    @Override
+    void end(Canvas currentCanvas) {
+        if (currentCanvas instanceof GLES20Canvas) {
+            ((GLES20Canvas) currentCanvas).resume();
+        }
+    }
+
+    @Override
+    HardwareCanvas start(Canvas currentCanvas) {
+        if (currentCanvas instanceof GLES20Canvas) {
+            ((GLES20Canvas) currentCanvas).interrupt();
+        }
+        return getCanvas();
+    }
+}
diff --git a/core/java/android/view/GLES20TextureLayer.java b/core/java/android/view/GLES20TextureLayer.java
new file mode 100644
index 0000000..21fbdfc
--- /dev/null
+++ b/core/java/android/view/GLES20TextureLayer.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.graphics.Canvas;
+import android.graphics.SurfaceTexture;
+
+/**
+ * An OpenGL ES 2.0 implementation of {@link HardwareLayer}. This
+ * implementation can be used as a texture. Rendering into this
+ * layer is not controlled by a {@link HardwareCanvas}.
+ */
+class GLES20TextureLayer extends GLES20Layer {
+    private int mTexture;
+    private SurfaceTexture mSurface;
+
+    GLES20TextureLayer() {
+        int[] layerInfo = new int[2];
+        mLayer = GLES20Canvas.nCreateTextureLayer(layerInfo);
+
+        if (mLayer != 0) {
+            mTexture = layerInfo[0];
+            mFinalizer = new Finalizer(mLayer);
+        } else {
+            mFinalizer = null;
+        }        
+    }
+
+    @Override
+    boolean isValid() {
+        return mLayer != 0 && mTexture != 0;
+    }
+
+    @Override
+    void resize(int width, int height) {
+    }
+
+    @Override
+    HardwareCanvas getCanvas() {
+        return null;
+    }
+
+    @Override
+    HardwareCanvas start(Canvas currentCanvas) {
+        return null;
+    }
+
+    @Override
+    void end(Canvas currentCanvas) {
+    }
+
+    SurfaceTexture getSurfaceTexture() {
+        if (mSurface == null) {
+            mSurface = new SurfaceTexture(mTexture);
+        }
+        return mSurface;
+    }
+
+    void update(int width, int height, float[] textureTransform) {
+        GLES20Canvas.nUpdateTextureLayer(mLayer, width, height, textureTransform);
+    }
+}
diff --git a/core/java/android/view/HardwareLayer.java b/core/java/android/view/HardwareLayer.java
index d01b8ce..86dec3f 100644
--- a/core/java/android/view/HardwareLayer.java
+++ b/core/java/android/view/HardwareLayer.java
@@ -26,12 +26,24 @@
  * drawn several times.
  */
 abstract class HardwareLayer {
+    /**
+     * Indicates an unknown dimension (width or height.)
+     */
+    static final int DIMENSION_UNDEFINED = -1;
+    
     int mWidth;
     int mHeight;
 
     final boolean mOpaque;
 
     /**
+     * Creates a new hardware layer with undefined dimensions.
+     */
+    HardwareLayer() {
+        this(DIMENSION_UNDEFINED, DIMENSION_UNDEFINED, false);
+    }
+
+    /**
      * Creates a new hardware layer at least as large as the supplied
      * dimensions.
      * 
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index 66f37f2..5b2983d 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -20,6 +20,7 @@
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.Rect;
+import android.graphics.SurfaceTexture;
 import android.os.*;
 import android.util.EventLog;
 import android.util.Log;
@@ -166,6 +167,14 @@
     abstract DisplayList createDisplayList(View v);
 
     /**
+     * Creates a new hardware layer. A hardware layer built by calling this
+     * method will be treated as a texture layer, instead of as a render target.
+     * 
+     * @return A hardware layer
+     */    
+    abstract HardwareLayer createHardwareLayer();
+    
+    /**
      * Creates a new hardware layer.
      * 
      * @param width The minimum width of the layer
@@ -175,6 +184,29 @@
      * @return A hardware layer
      */
     abstract HardwareLayer createHardwareLayer(int width, int height, boolean isOpaque);
+
+    /**
+     * Creates a new {@link SurfaceTexture} that can be used to render into the
+     * specified hardware layer.
+     * 
+     *
+     * @param layer The layer to render into using a {@link android.graphics.SurfaceTexture}
+     * 
+     * @return A {@link SurfaceTexture}
+     */
+    abstract SurfaceTexture createSuraceTexture(HardwareLayer layer);
+
+    /**
+     * Updates the specified layer.
+     * 
+     * @param layer The hardware layer to update
+     * @param width The layer's width
+     * @param height The layer's height
+     * @param textureTransform A 4x4 column-first transform matrix to apply to
+     *        texture coordinates
+     */
+    abstract void updateTextureLayer(HardwareLayer layer, int width, int height,
+            float[] textureTransform);    
     
     /**
      * Initializes the hardware renderer for the specified surface and setup the
@@ -856,10 +888,26 @@
         DisplayList createDisplayList(View v) {
             return new GLES20DisplayList(v);
         }
-        
+
+        @Override
+        HardwareLayer createHardwareLayer() {
+            return new GLES20TextureLayer();
+        }
+
         @Override
         HardwareLayer createHardwareLayer(int width, int height, boolean isOpaque) {
-            return new GLES20Layer(width, height, isOpaque);
+            return new GLES20RenderLayer(width, height, isOpaque);
+        }
+
+        @Override
+        SurfaceTexture createSuraceTexture(HardwareLayer layer) {
+            return ((GLES20TextureLayer) layer).getSurfaceTexture();
+        }
+
+        @Override
+        void updateTextureLayer(HardwareLayer layer, int width, int height,
+                float[] textureTransform) {
+            ((GLES20TextureLayer) layer).update(width, height, textureTransform);
         }
 
         static HardwareRenderer create(boolean translucent) {
diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java
new file mode 100644
index 0000000..5ece15c
--- /dev/null
+++ b/core/java/android/view/TextureView.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.SurfaceTexture;
+import android.util.AttributeSet;
+import android.util.Log;
+
+/**
+ * <p>A TextureView can be used to display a content stream. Such a content
+ * stream can for instance be a video or an OpenGL scene. The content stream
+ * can come from the application's process as well as a remote process.</p>
+ * 
+ * <p>TextureView can only be used in a hardware accelerated window. When
+ * rendered in software, TextureView will draw nothing.</p>
+ * 
+ * <p>Unlike {@link SurfaceView}, TextureView does not create a separate
+ * window but behaves as a regular View. This key difference allows a
+ * TextureView to be moved, transformed, animated, etc. For instance, you
+ * can make a TextureView semi-translucent by calling
+ * <code>myView.setAlpha(0.5f)</code>.</p>
+ * 
+ * <p>Using a TextureView is simple: all you need to do is get its
+ * {@link SurfaceTexture}. The {@link SurfaceTexture} can then be used to
+ * render content. The following example demonstrates how to render the 
+ * camera preview into a TextureView:</p>
+ * 
+ * <pre>
+ *  public class LiveCameraActivity extends Activity implements TextureView.SurfaceTextureListener {
+ *      private Camera mCamera;
+ *      private TextureView mTextureView;
+ *
+ *      @Override
+ *      protected void onCreate(Bundle savedInstanceState) {
+ *          super.onCreate(savedInstanceState);
+ *
+ *          mTextureView = new TextureView(this);
+ *          mTextureView.setSurfaceTextureListener(this);
+ *
+ *          setContentView(mTextureView);
+ *      }
+ *
+ *      @Override
+ *      protected void onDestroy() {
+ *          super.onDestroy();
+ *
+ *          mCamera.stopPreview();
+ *          mCamera.release();
+ *      }
+ *
+ *      @Override
+ *      public void onSurfaceTextureAvailable(SurfaceTexture surface) {
+ *          mCamera = Camera.open();
+ *
+ *          try {
+ *              mCamera.setPreviewTexture(surface);
+ *              mCamera.startPreview();
+ *          } catch (IOException ioe) {
+ *              // Something bad happened
+ *          }
+ *      }
+ *  }
+ * </pre>
+ * 
+ * <p>A TextureView's SurfaceTexture can be obtained either by invoking
+ * {@link #getSurfaceTexture()} or by using a {@link SurfaceTextureListener}.
+ * It is important to know that a SurfaceTexture is available only after the
+ * TextureView is attached to a window (and {@link #onAttachedToWindow()} has
+ * been invoked.) It is therefore highly recommended you use a listener to
+ * be notified when the SurfaceTexture becomes available.</p>
+ * 
+ * @see SurfaceView
+ * @see SurfaceTexture
+ */
+public class TextureView extends View {
+    private HardwareLayer mLayer;
+    private SurfaceTexture mSurface;
+    private SurfaceTextureListener mListener;
+    
+    private final float[] mTextureTransform = new float[16];
+
+    private final Runnable mUpdateLayerAction = new Runnable() {
+        @Override
+        public void run() {
+            updateLayer();
+        }
+    };
+
+    /**
+     * Creates a new TextureView.
+     * 
+     * @param context The context to associate this view with.
+     */
+    public TextureView(Context context) {
+        super(context);
+        init();
+    }
+
+    /**
+     * Creates a new TextureView.
+     * 
+     * @param context The context to associate this view with.
+     * @param attrs The attributes of the XML tag that is inflating the view.
+     */
+    @SuppressWarnings({"UnusedDeclaration"})
+    public TextureView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init();
+    }
+
+    /**
+     * Creates a new TextureView.
+     * 
+     * @param context The context to associate this view with.
+     * @param attrs The attributes of the XML tag that is inflating the view.
+     * @param defStyle The default style to apply to this view. If 0, no style
+     *        will be applied (beyond what is included in the theme). This may
+     *        either be an attribute resource, whose value will be retrieved
+     *        from the current theme, or an explicit style resource.
+     */
+    @SuppressWarnings({"UnusedDeclaration"})
+    public TextureView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        init();
+    }
+
+    private void init() {
+        mLayerPaint = new Paint();
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
+        if (!isHardwareAccelerated()) {
+            Log.w("TextureView", "A TextureView or a subclass can only be "
+                    + "used with hardware acceleration enabled.");
+        }
+    }
+
+    /**
+     * The layer type of a TextureView is ignored since a TextureView is always
+     * considered to act as a hardware layer. The optional paint supplied to this
+     * method will however be taken into account when rendering the content of
+     * this TextureView.
+     * 
+     * @param layerType The ype of layer to use with this view, must be one of
+     *        {@link #LAYER_TYPE_NONE}, {@link #LAYER_TYPE_SOFTWARE} or
+     *        {@link #LAYER_TYPE_HARDWARE}
+     * @param paint The paint used to compose the layer. This argument is optional
+     *        and can be null. It is ignored when the layer type is
+     *        {@link #LAYER_TYPE_NONE}
+     */
+    @Override
+    public void setLayerType(int layerType, Paint paint) {
+        if (paint != mLayerPaint) {
+            mLayerPaint = paint;
+            invalidate();
+        }
+    }
+
+    /**
+     * Always returns {@link #LAYER_TYPE_HARDWARE}.
+     */
+    @Override
+    public int getLayerType() {
+        return LAYER_TYPE_HARDWARE;
+    }
+
+    /**
+     * Calling this method has no effect.
+     */
+    @Override
+    public void buildLayer() {
+    }
+
+    /**
+     * Subclasses of TextureView cannot do their own rendering
+     * with the {@link Canvas} object.
+     * 
+     * @param canvas The Canvas to which the View is rendered.
+     */
+    @Override
+    public final void draw(Canvas canvas) {
+        super.draw(canvas);
+    }
+
+    /**
+     * Subclasses of TextureView cannot do their own rendering
+     * with the {@link Canvas} object.
+     * 
+     * @param canvas The Canvas to which the View is rendered.
+     */
+    @Override
+    protected final void onDraw(Canvas canvas) {
+    }
+
+    @Override
+    HardwareLayer getHardwareLayer() {
+        if (mAttachInfo == null || mAttachInfo.mHardwareRenderer == null) {
+            return null;
+        }
+
+        if (mLayer == null) {
+            mLayer = mAttachInfo.mHardwareRenderer.createHardwareLayer();
+            mSurface = mAttachInfo.mHardwareRenderer.createSuraceTexture(mLayer);
+
+            mSurface.setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() {
+                @Override
+                public void onFrameAvailable(SurfaceTexture surfaceTexture) {
+                    // Per SurfaceTexture's documentation, the callback may be invoked
+                    // from an arbitrary thread
+                    post(mUpdateLayerAction);
+                }
+            });
+
+            if (mListener != null) {
+                mListener.onSurfaceTextureAvailable(mSurface);
+            }
+        }
+
+        return mLayer;
+    }
+
+    private void updateLayer() {
+        if (mAttachInfo == null || mAttachInfo.mHardwareRenderer == null) {
+            return;
+        }
+
+        mSurface.updateTexImage();
+        mSurface.getTransformMatrix(mTextureTransform);
+
+        mAttachInfo.mHardwareRenderer.updateTextureLayer(mLayer, getWidth(), getHeight(),
+                mTextureTransform);
+
+        invalidate();
+    }
+
+    /**
+     * Returns the {@link SurfaceTexture} used by this view. This method
+     * may return null if the view is not attached to a window.
+     */
+    public SurfaceTexture getSurfaceTexture() {
+        return mSurface;
+    }
+
+    /**
+     * Returns the {@link SurfaceTextureListener} currently associated with this
+     * texture view.
+     * 
+     * @see #setSurfaceTextureListener(android.view.TextureView.SurfaceTextureListener) 
+     * @see SurfaceTextureListener
+     */
+    public SurfaceTextureListener getSurfaceTextureListener() {
+        return mListener;
+    }
+
+    /**
+     * Sets the {@link SurfaceTextureListener} used to listen to surface
+     * texture events.
+     * 
+     * @see #getSurfaceTextureListener() 
+     * @see SurfaceTextureListener
+     */
+    public void setSurfaceTextureListener(SurfaceTextureListener listener) {
+        mListener = listener;
+    }
+
+    /**
+     * This listener can be used to be notified when the surface texture
+     * associated with this texture view is available.
+     */
+    public static interface SurfaceTextureListener {
+        /**
+         * Invoked when a {@link TextureView}'s SurfaceTexture is ready for use.
+         * 
+         * @param surface The surface returned by
+         *                {@link android.view.TextureView#getSurfaceTexture()}
+         */
+        public void onSurfaceTextureAvailable(SurfaceTexture surface);
+    }
+}
diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp
index 314c2ee..af7639a 100644
--- a/core/jni/android_view_GLES20Canvas.cpp
+++ b/core/jni/android_view_GLES20Canvas.cpp
@@ -539,6 +539,19 @@
     return NULL;
 }
 
+static Layer* android_view_GLES20Canvas_createTextureLayer(JNIEnv* env, jobject clazz,
+        jintArray layerInfo) {
+    Layer* layer = LayerRenderer::createTextureLayer();
+
+    if (layer) {
+        jint* storage = env->GetIntArrayElements(layerInfo, NULL);
+        storage[0] = layer->texture;
+        env->ReleaseIntArrayElements(layerInfo, storage, 0);
+    }
+
+    return layer;
+}
+
 static Layer* android_view_GLES20Canvas_createLayer(JNIEnv* env, jobject clazz,
         jint width, jint height, jboolean isOpaque, jintArray layerInfo) {
     Layer* layer = LayerRenderer::createLayer(width, height, isOpaque);
@@ -563,6 +576,13 @@
     env->ReleaseIntArrayElements(layerInfo, storage, 0);
 }
 
+static void android_view_GLES20Canvas_updateTextureLayer(JNIEnv* env, jobject clazz,
+        Layer* layer, jint width, jint height, jfloatArray texTransform) {
+    jfloat* transform = env->GetFloatArrayElements(texTransform, NULL);
+    LayerRenderer::updateTextureLayer(layer, width, height, transform);
+    env->ReleaseFloatArrayElements(texTransform, transform, 0);
+}
+
 static void android_view_GLES20Canvas_destroyLayer(JNIEnv* env, jobject clazz, Layer* layer) {
     LayerRenderer::destroyLayer(layer);
 }
@@ -696,6 +716,8 @@
     { "nCreateLayerRenderer",    "(I)I",       (void*) android_view_GLES20Canvas_createLayerRenderer },
     { "nCreateLayer",            "(IIZ[I)I",   (void*) android_view_GLES20Canvas_createLayer },
     { "nResizeLayer",            "(III[I)V" ,  (void*) android_view_GLES20Canvas_resizeLayer },
+    { "nCreateTextureLayer",     "([I)I",      (void*) android_view_GLES20Canvas_createTextureLayer },
+    { "nUpdateTextureLayer",     "(III[F)V" ,  (void*) android_view_GLES20Canvas_updateTextureLayer },
     { "nDestroyLayer",           "(I)V",       (void*) android_view_GLES20Canvas_destroyLayer },
     { "nDestroyLayerDeferred",   "(I)V",       (void*) android_view_GLES20Canvas_destroyLayerDeferred },
     { "nDrawLayer",              "(IIFFI)V",   (void*) android_view_GLES20Canvas_drawLayer },
diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h
index 6c4a2a9..16566b8 100644
--- a/libs/hwui/Layer.h
+++ b/libs/hwui/Layer.h
@@ -45,6 +45,8 @@
         mesh = NULL;
         meshIndices = NULL;
         meshElementCount = 0;
+        isCacheable = true;
+        isTextureLayer = false;
     }
 
     ~Layer() {
@@ -137,6 +139,22 @@
     TextureVertex* mesh;
     uint16_t* meshIndices;
     GLsizei meshElementCount;
+
+    /**
+     * If set to true (by default), the layer can be reused.
+     */
+    bool isCacheable;
+
+    /**
+     * When set to true, this layer must be treated as a texture
+     * layer.
+     */
+    bool isTextureLayer;
+
+    /**
+     * Optional texture coordinates transform.
+     */
+    mat4 texTransform;
 }; // struct Layer
 
 }; // namespace uirenderer
diff --git a/libs/hwui/LayerCache.cpp b/libs/hwui/LayerCache.cpp
index a9710ad..b2d795f 100644
--- a/libs/hwui/LayerCache.cpp
+++ b/libs/hwui/LayerCache.cpp
@@ -154,6 +154,8 @@
 }
 
 bool LayerCache::put(Layer* layer) {
+    if (!layer->isCacheable) return false;
+
     const uint32_t size = layer->width * layer->height * 4;
     // Don't even try to cache a layer that's bigger than the cache
     if (size < mMaxSize) {
diff --git a/libs/hwui/LayerRenderer.cpp b/libs/hwui/LayerRenderer.cpp
index ca1e7ae..e167336 100644
--- a/libs/hwui/LayerRenderer.cpp
+++ b/libs/hwui/LayerRenderer.cpp
@@ -20,6 +20,7 @@
 
 #include "LayerCache.h"
 #include "LayerRenderer.h"
+#include "Matrix.h"
 #include "Properties.h"
 #include "Rect.h"
 
@@ -165,6 +166,40 @@
 // Layers management
 ///////////////////////////////////////////////////////////////////////////////
 
+Layer* LayerRenderer::createTextureLayer() {
+    LAYER_RENDERER_LOGD("Creating new texture layer");
+
+    Layer* layer = new Layer(0, 0);
+    layer->isCacheable = false;
+    layer->isTextureLayer = true;
+    layer->blend = true;
+    layer->empty = true;
+    layer->fbo = 0;
+    layer->colorFilter = NULL;
+    layer->fbo = 0;
+    layer->layer.set(0.0f, 0.0f, 0.0f, 0.0f);
+    layer->texCoords.set(0.0f, 1.0f, 0.0f, 1.0f);
+    layer->alpha = 255;
+    layer->mode = SkXfermode::kSrcOver_Mode;
+    layer->colorFilter = NULL;
+    layer->region.clear();
+
+    glActiveTexture(GL_TEXTURE0);
+
+    glGenTextures(1, &layer->texture);
+    glBindTexture(GL_TEXTURE_EXTERNAL_OES, layer->texture);
+
+    glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
+
+    glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+    glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+    glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+    glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+    return layer;
+}
+
 Layer* LayerRenderer::createLayer(uint32_t width, uint32_t height, bool isOpaque) {
     LAYER_RENDERER_LOGD("Creating new layer %dx%d", width, height);
 
@@ -244,6 +279,18 @@
     return true;
 }
 
+void LayerRenderer::updateTextureLayer(Layer* layer, uint32_t width, uint32_t height,
+        float* transform) {
+    if (layer) {
+        layer->width = width;
+        layer->height = height;
+        layer->layer.set(0.0f, 0.0f, width, height);
+        layer->region.set(width, height);
+        layer->regionRect.set(0.0f, 0.0f, width, height);
+        layer->texTransform.load(transform);
+    }
+}
+
 void LayerRenderer::destroyLayer(Layer* layer) {
     if (layer) {
         LAYER_RENDERER_LOGD("Destroying layer, fbo = %d", layer->fbo);
diff --git a/libs/hwui/LayerRenderer.h b/libs/hwui/LayerRenderer.h
index d2f565e..b3cd5db 100644
--- a/libs/hwui/LayerRenderer.h
+++ b/libs/hwui/LayerRenderer.h
@@ -53,8 +53,11 @@
     Region* getRegion();
     GLint getTargetFbo();
 
+    static Layer* createTextureLayer();
     static Layer* createLayer(uint32_t width, uint32_t height, bool isOpaque = false);
     static bool resizeLayer(Layer* layer, uint32_t width, uint32_t height);
+    static void updateTextureLayer(Layer* layer, uint32_t width, uint32_t height,
+            float* transform);
     static void destroyLayer(Layer* layer);
     static void destroyLayerDeferred(Layer* layer);
 
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index e926d99..4dde342 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -633,15 +633,43 @@
     }
 }
 
+void OpenGLRenderer::drawTextureLayer(Layer* layer, const Rect& rect) {
+    float alpha = layer->alpha / 255.0f;
+
+    setupDraw();
+    setupDrawWithExternalTexture();
+    setupDrawColor(alpha, alpha, alpha, alpha);
+    setupDrawColorFilter();
+    setupDrawBlending(layer->blend, layer->mode);
+    setupDrawProgram();
+    setupDrawModelView(rect.left, rect.top, rect.right, rect.bottom);
+    setupDrawPureColorUniforms();
+    setupDrawColorFilterUniforms();
+    setupDrawExternalTexture(layer->texture);
+    setupDrawTextureTransform(layer->texTransform);
+    setupDrawMesh(&mMeshVertices[0].position[0], &mMeshVertices[0].texture[0]);
+
+    glDrawArrays(GL_TRIANGLE_STRIP, 0, gMeshCount);
+
+    finishDrawTexture();
+}
+
 void OpenGLRenderer::composeLayerRect(Layer* layer, const Rect& rect, bool swap) {
-    const Rect& texCoords = layer->texCoords;
-    resetDrawTextureTexCoords(texCoords.left, texCoords.top, texCoords.right, texCoords.bottom);
+    if (!layer->isTextureLayer) {
+        const Rect& texCoords = layer->texCoords;
+        resetDrawTextureTexCoords(texCoords.left, texCoords.top,
+                texCoords.right, texCoords.bottom);
 
-    drawTextureMesh(rect.left, rect.top, rect.right, rect.bottom, layer->texture,
-            layer->alpha / 255.0f, layer->mode, layer->blend, &mMeshVertices[0].position[0],
-            &mMeshVertices[0].texture[0], GL_TRIANGLE_STRIP, gMeshCount, swap, swap);
+        drawTextureMesh(rect.left, rect.top, rect.right, rect.bottom, layer->texture,
+                layer->alpha / 255.0f, layer->mode, layer->blend, &mMeshVertices[0].position[0],
+                &mMeshVertices[0].texture[0], GL_TRIANGLE_STRIP, gMeshCount, swap, swap);
 
-    resetDrawTextureTexCoords(0.0f, 0.0f, 1.0f, 1.0f);
+        resetDrawTextureTexCoords(0.0f, 0.0f, 1.0f, 1.0f);
+    } else {
+        resetDrawTextureTexCoords(0.0f, 1.0f, 1.0f, 0.0f);
+        drawTextureLayer(layer, rect);
+        resetDrawTextureTexCoords(0.0f, 0.0f, 1.0f, 1.0f);
+    }
 }
 
 void OpenGLRenderer::composeLayerRegion(Layer* layer, const Rect& rect) {
@@ -882,6 +910,10 @@
     mDescription.hasAlpha8Texture = isAlpha8;
 }
 
+void OpenGLRenderer::setupDrawWithExternalTexture() {
+    mDescription.hasExternalTexture = true;
+}
+
 void OpenGLRenderer::setupDrawAALine() {
     mDescription.hasWidth = true;
 }
@@ -1055,6 +1087,19 @@
     glEnableVertexAttribArray(mTexCoordsSlot);
 }
 
+void OpenGLRenderer::setupDrawExternalTexture(GLuint texture) {
+    bindExternalTexture(texture);
+    glUniform1i(mCaches.currentProgram->getUniform("sampler"), mTextureUnit++);
+
+    mTexCoordsSlot = mCaches.currentProgram->getAttrib("texCoords");
+    glEnableVertexAttribArray(mTexCoordsSlot);
+}
+
+void OpenGLRenderer::setupDrawTextureTransform(mat4& transform) {
+    glUniformMatrix4fv(mCaches.currentProgram->getUniform("mainTextureTransform"), 1,
+            GL_FALSE, &transform.data[0]);
+}
+
 void OpenGLRenderer::setupDrawMesh(GLvoid* vertices, GLvoid* texCoords, GLuint vbo) {
     if (!vertices) {
         mCaches.bindMeshBuffer(vbo == 0 ? mCaches.meshBuffer : vbo);
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index 0276095..a3e12f6 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -248,6 +248,8 @@
      */
     void composeLayerRect(Layer* layer, const Rect& rect, bool swap = false);
 
+    void drawTextureLayer(Layer* layer, const Rect& rect);
+
     /**
      * Mark the layer as dirty at the specified coordinates. The coordinates
      * are transformed with the supplied matrix.
@@ -400,6 +402,14 @@
     }
 
     /**
+     * Binds the specified EGLImage texture. The texture unit must have been selected
+     * prior to calling this method.
+     */
+    inline void bindExternalTexture(GLuint texture) {
+        glBindTexture(GL_TEXTURE_EXTERNAL_OES, texture);
+    }
+
+    /**
      * Sets the wrap modes for the specified texture. The wrap modes are modified
      * only when needed.
      */
@@ -438,6 +448,7 @@
      * Various methods to setup OpenGL rendering.
      */
     void setupDrawWithTexture(bool isAlpha8 = false);
+    void setupDrawWithExternalTexture();
     void setupDrawAALine();
     void setupDrawPoint(float pointSize);
     void setupDrawColor(int color);
@@ -466,6 +477,8 @@
     void setupDrawColorFilterUniforms();
     void setupDrawSimpleMesh();
     void setupDrawTexture(GLuint texture);
+    void setupDrawExternalTexture(GLuint texture);
+    void setupDrawTextureTransform(mat4& transform);
     void setupDrawMesh(GLvoid* vertices, GLvoid* texCoords = NULL, GLuint vbo = 0);
     void setupDrawVertices(GLvoid* vertices);
     void setupDrawAALine(GLvoid* vertices, GLvoid* distanceCoords, float strokeWidth);
diff --git a/libs/hwui/ProgramCache.cpp b/libs/hwui/ProgramCache.cpp
index 80b1917..62ac2ba 100644
--- a/libs/hwui/ProgramCache.cpp
+++ b/libs/hwui/ProgramCache.cpp
@@ -41,6 +41,8 @@
         "attribute vec2 texCoords;\n";
 const char* gVS_Header_Attributes_Distance =
         "attribute float vtxDistance;\n";
+const char* gVS_Header_Uniforms_TextureTransform =
+        "uniform mat4 mainTextureTransform;\n";
 const char* gVS_Header_Uniforms =
         "uniform mat4 transform;\n";
 const char* gVS_Header_Uniforms_IsPoint =
@@ -76,6 +78,8 @@
         "\nvoid main(void) {\n";
 const char* gVS_Main_OutTexCoords =
         "    outTexCoords = texCoords;\n";
+const char* gVS_Main_OutTransformedTexCoords =
+        "    outTexCoords = (mainTextureTransform * vec4(texCoords, 0.0, 1.0)).xy;\n";
 const char* gVS_Main_OutGradient[3] = {
         // Linear
         "    linear = vec2((screenSpace * position).x, 0.5);\n",
@@ -103,6 +107,8 @@
 
 const char* gFS_Header_Extension_FramebufferFetch =
         "#extension GL_NV_shader_framebuffer_fetch : enable\n\n";
+const char* gFS_Header_Extension_ExternalTexture =
+        "#extension GL_OES_EGL_image_external : require\n\n";
 const char* gFS_Header =
         "precision mediump float;\n\n";
 const char* gFS_Uniforms_Color =
@@ -116,6 +122,8 @@
         "uniform float pointSize;\n";
 const char* gFS_Uniforms_TextureSampler =
         "uniform sampler2D sampler;\n";
+const char* gFS_Uniforms_ExternalTextureSampler =
+        "uniform samplerExternalOES sampler;\n";
 const char* gFS_Uniforms_GradientSampler[3] = {
         // Linear
         "uniform sampler2D gradientSampler;\n",
@@ -369,7 +377,7 @@
 String8 ProgramCache::generateVertexShader(const ProgramDescription& description) {
     // Add attributes
     String8 shader(gVS_Header_Attributes);
-    if (description.hasTexture) {
+    if (description.hasTexture || description.hasExternalTexture) {
         shader.append(gVS_Header_Attributes_TexCoords);
     }
     if (description.hasWidth) {
@@ -377,6 +385,9 @@
     }
     // Uniforms
     shader.append(gVS_Header_Uniforms);
+    if (description.hasExternalTexture) {
+        shader.append(gVS_Header_Uniforms_TextureTransform);
+    }
     if (description.hasGradient) {
         shader.append(gVS_Header_Uniforms_HasGradient[description.gradientType]);
     }
@@ -387,7 +398,7 @@
         shader.append(gVS_Header_Uniforms_IsPoint);
     }
     // Varyings
-    if (description.hasTexture) {
+    if (description.hasTexture || description.hasExternalTexture) {
         shader.append(gVS_Header_Varyings_HasTexture);
     }
     if (description.hasWidth) {
@@ -407,6 +418,9 @@
         if (description.hasTexture) {
             shader.append(gVS_Main_OutTexCoords);
         }
+        if (description.hasExternalTexture) {
+            shader.append(gVS_Main_OutTransformedTexCoords);
+        }
         if (description.hasWidth) {
             shader.append(gVS_Main_Width);
         }
@@ -440,11 +454,14 @@
     if (blendFramebuffer) {
         shader.append(gFS_Header_Extension_FramebufferFetch);
     }
+    if (description.hasExternalTexture) {
+        shader.append(gFS_Header_Extension_ExternalTexture);
+    }
 
     shader.append(gFS_Header);
 
     // Varyings
-    if (description.hasTexture) {
+    if (description.hasTexture || description.hasExternalTexture) {
         shader.append(gVS_Header_Varyings_HasTexture);
     }
     if (description.hasWidth) {
@@ -461,7 +478,7 @@
 
     // Uniforms
     int modulateOp = MODULATE_OP_NO_MODULATE;
-    const bool singleColor = !description.hasTexture &&
+    const bool singleColor = !description.hasTexture && !description.hasExternalTexture &&
             !description.hasGradient && !description.hasBitmap;
 
     if (description.modulate || singleColor) {
@@ -471,6 +488,9 @@
     if (description.hasTexture) {
         shader.append(gFS_Uniforms_TextureSampler);
     }
+    if (description.hasExternalTexture) {
+        shader.append(gFS_Uniforms_ExternalTextureSampler);
+    }
     if (description.hasWidth) {
         shader.append(gFS_Uniforms_Width);
     }
@@ -487,11 +507,11 @@
         bool fast = false;
 
         const bool noShader = !description.hasGradient && !description.hasBitmap;
-        const bool singleTexture = description.hasTexture &&
+        const bool singleTexture = (description.hasTexture || description.hasExternalTexture) &&
                 !description.hasAlpha8Texture && noShader;
         const bool singleA8Texture = description.hasTexture &&
                 description.hasAlpha8Texture && noShader;
-        const bool singleGradient = !description.hasTexture &&
+        const bool singleGradient = !description.hasTexture && !description.hasExternalTexture &&
                 description.hasGradient && !description.hasBitmap &&
                 description.gradientType == ProgramDescription::kGradientLinear;
 
@@ -554,7 +574,7 @@
     // Begin the shader
     shader.append(gFS_Main); {
         // Stores the result in fragColor directly
-        if (description.hasTexture) {
+        if (description.hasTexture || description.hasExternalTexture) {
             if (description.hasAlpha8Texture) {
                 if (!description.hasGradient && !description.hasBitmap) {
                     shader.append(gFS_Main_FetchA8Texture[modulateOp]);
diff --git a/libs/hwui/ProgramCache.h b/libs/hwui/ProgramCache.h
index 18d98cb..70909fd 100644
--- a/libs/hwui/ProgramCache.h
+++ b/libs/hwui/ProgramCache.h
@@ -77,6 +77,8 @@
 
 #define PROGRAM_HAS_WIDTH_SHIFT 37
 
+#define PROGRAM_HAS_EXTERNAL_TEXTURE_SHIFT 38
+
 ///////////////////////////////////////////////////////////////////////////////
 // Types
 ///////////////////////////////////////////////////////////////////////////////
@@ -113,6 +115,7 @@
     // Texturing
     bool hasTexture;
     bool hasAlpha8Texture;
+    bool hasExternalTexture;
 
     // Modulate, this should only be set when setColor() return true
     bool modulate;
@@ -151,6 +154,7 @@
     void reset() {
         hasTexture = false;
         hasAlpha8Texture = false;
+        hasExternalTexture = false;
 
         hasWidth = false;
 
@@ -240,6 +244,7 @@
         if (modulate) key |= programid(0x1) << PROGRAM_MODULATE_SHIFT;
         if (isPoint) key |= programid(0x1) << PROGRAM_IS_POINT_SHIFT;
         if (hasWidth) key |= programid(0x1) << PROGRAM_HAS_WIDTH_SHIFT;
+        if (hasExternalTexture) key |= programid(0x1) << PROGRAM_HAS_EXTERNAL_TEXTURE_SHIFT;
         return key;
     }
 
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index c763b1d..3a3bfd8 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -18,6 +18,11 @@
     package="com.android.test.hwui">
 
     <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.CAMERA" />
+
+    <uses-feature android:name="android.hardware.camera" />
+    <uses-feature android:name="android.hardware.camera.autofocus" />
+
     <uses-sdk android:minSdkVersion="11" />
     
     <application
@@ -25,6 +30,15 @@
         android:hardwareAccelerated="true">
 
         <activity
+                android:name="TextureViewActivity"
+                android:label="_TextureView">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        
+        <activity
                 android:name="BitmapMeshActivity"
                 android:label="_BitmapMesh">
             <intent-filter>
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/TextureViewActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/TextureViewActivity.java
new file mode 100644
index 0000000..4726672
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/TextureViewActivity.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.hwui;
+
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.app.Activity;
+import android.graphics.SurfaceTexture;
+import android.hardware.Camera;
+import android.os.Bundle;
+import android.view.Gravity;
+import android.view.TextureView;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import java.io.IOException;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class TextureViewActivity extends Activity implements TextureView.SurfaceTextureListener {
+    private Camera mCamera;
+    private TextureView mTextureView;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mTextureView = new TextureView(this);
+        mTextureView.setSurfaceTextureListener(this);
+
+        setContentView(mTextureView, new FrameLayout.LayoutParams(500, 400, Gravity.CENTER));
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+
+        mCamera.stopPreview();
+        mCamera.release();
+    }
+
+    @Override
+    public void onSurfaceTextureAvailable(SurfaceTexture surface) {
+        mCamera = Camera.open();
+
+        try {
+            mCamera.setPreviewTexture(surface);
+        } catch (IOException t) {
+            android.util.Log.e("TextureView", "Cannot set preview texture target!", t);
+        }
+
+        mCamera.startPreview();
+
+        mTextureView.setCameraDistance(5000);
+
+        ObjectAnimator animator = ObjectAnimator.ofFloat(mTextureView, "rotationY", 0.0f, 360.0f);
+        animator.setRepeatMode(ObjectAnimator.REVERSE);
+        animator.setRepeatCount(ObjectAnimator.INFINITE);
+        animator.setDuration(4000);
+        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                ((View) mTextureView.getParent()).invalidate();
+            }
+        });
+        animator.start();
+
+        animator = ObjectAnimator.ofFloat(mTextureView, "alpha", 1.0f, 0.0f);
+        animator.setRepeatMode(ObjectAnimator.REVERSE);
+        animator.setRepeatCount(ObjectAnimator.INFINITE);
+        animator.setDuration(4000);
+        animator.start();
+    }
+}