Add TextureView.getBitmap()

This API can be used to get a Bitmap copy of the content of a
TextureView.

Change-Id: I07522216c353720fba5cab333174f58f484eb911
diff --git a/api/current.txt b/api/current.txt
index 3724d16..7c9c851 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -21566,8 +21566,12 @@
     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.Bitmap getBitmap();
+    method public android.graphics.Bitmap getBitmap(int, int);
+    method public android.graphics.Bitmap getBitmap(android.graphics.Bitmap);
     method public android.graphics.SurfaceTexture getSurfaceTexture();
     method public android.view.TextureView.SurfaceTextureListener getSurfaceTextureListener();
+    method public boolean isAvailable();
     method protected final void onDraw(android.graphics.Canvas);
     method public void setSurfaceTextureListener(android.view.TextureView.SurfaceTextureListener);
   }
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
index d5cad96..383bfb3 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/GLES20Canvas.java
@@ -166,6 +166,7 @@
     static native void nUpdateTextureLayer(int layerId, int width, int height, int surface);
     static native void nDestroyLayer(int layerId);
     static native void nDestroyLayerDeferred(int layerId);
+    static native boolean nCopyLayer(int layerId, int bitmap);
 
     ///////////////////////////////////////////////////////////////////////////
     // Canvas management
diff --git a/core/java/android/view/GLES20Layer.java b/core/java/android/view/GLES20Layer.java
index bc191a6..69dfc2b 100644
--- a/core/java/android/view/GLES20Layer.java
+++ b/core/java/android/view/GLES20Layer.java
@@ -17,6 +17,8 @@
 
 package android.view;
 
+import android.graphics.Bitmap;
+
 /**
  * An OpenGL ES 2.0 implementation of {@link HardwareLayer}.
  */
@@ -40,7 +42,10 @@
         return mLayer;
     }
 
-    
+    boolean copyInto(Bitmap bitmap) {
+        return GLES20Canvas.nCopyLayer(mLayer, bitmap.mNativeBitmap);
+    }    
+
     @Override
     void destroy() {
         if (mFinalizer != null) mFinalizer.destroy();
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index 2611ec0..5944bd4 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -17,6 +17,7 @@
 
 package android.view;
 
+import android.graphics.Bitmap;
 import android.graphics.Paint;
 import android.graphics.Rect;
 import android.graphics.SurfaceTexture;
@@ -204,8 +205,18 @@
      * @param surface The surface to update
      */
     abstract void updateTextureLayer(HardwareLayer layer, int width, int height,
-            SurfaceTexture surface);    
-    
+            SurfaceTexture surface);
+
+    /**
+     * Copies the content of the specified layer into the specified bitmap.
+     * 
+     * @param layer The hardware layer to copy
+     * @param bitmap The bitmap to copy the layer into
+     * 
+     * @return True if the copy was successful, false otherwise
+     */
+    abstract boolean copyLayer(HardwareLayer layer, Bitmap bitmap);    
+
     /**
      * Initializes the hardware renderer for the specified surface and setup the
      * renderer for drawing, if needed. This is invoked when the ViewAncestor has
@@ -814,6 +825,11 @@
             ((GLES20TextureLayer) layer).update(width, height, surface.mSurfaceTexture);
         }
 
+        @Override
+        boolean copyLayer(HardwareLayer layer, Bitmap bitmap) {
+            return ((GLES20Layer) layer).copyInto(bitmap);
+        }
+
         static HardwareRenderer create(boolean translucent) {
             if (GLES20Canvas.isAvailable()) {
                 return new Gl20Renderer(translucent);
diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java
index bc1ad3c..4daa892 100644
--- a/core/java/android/view/TextureView.java
+++ b/core/java/android/view/TextureView.java
@@ -17,6 +17,7 @@
 package android.view;
 
 import android.content.Context;
+import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.SurfaceTexture;
@@ -89,6 +90,8 @@
  * @see SurfaceTexture
  */
 public class TextureView extends View {
+    private static final String LOG_TAG = "TextureView";
+
     private HardwareLayer mLayer;
     private SurfaceTexture mSurface;
     private SurfaceTextureListener mListener;
@@ -148,7 +151,7 @@
         super.onAttachedToWindow();
 
         if (!isHardwareAccelerated()) {
-            Log.w("TextureView", "A TextureView or a subclass can only be "
+            Log.w(LOG_TAG, "A TextureView or a subclass can only be "
                     + "used with hardware acceleration enabled.");
         }
     }
@@ -293,8 +296,95 @@
     }
 
     /**
+     * <p>Returns a {@link android.graphics.Bitmap} representation of the content
+     * of the associated surface texture. If the surface texture is not available,
+     * this method returns null.</p>
+     * 
+     * <p>The bitmap returned by this method uses the {@link Bitmap.Config#ARGB_8888}
+     * pixel format and its dimensions are the same as this view's.</p>
+     * 
+     * <p><strong>Do not</strong> invoke this method from a drawing method
+     * ({@link #onDraw(android.graphics.Canvas)} for instance).</p>
+     * 
+     * @return A valid {@link Bitmap.Config#ARGB_8888} bitmap, or null if the surface
+     *         texture is not available or the width &lt;= 0 or the height &lt;= 0
+     * 
+     * @see #isAvailable() 
+     * @see #getBitmap(android.graphics.Bitmap) 
+     * @see #getBitmap(int, int) 
+     */
+    public Bitmap getBitmap() {
+        return getBitmap(getWidth(), getHeight());
+    }
+
+    /**
+     * <p>Returns a {@link android.graphics.Bitmap} representation of the content
+     * of the associated surface texture. If the surface texture is not available,
+     * this method returns null.</p>
+     * 
+     * <p>The bitmap returned by this method uses the {@link Bitmap.Config#ARGB_8888}
+     * pixel format.</p>
+     * 
+     * <p><strong>Do not</strong> invoke this method from a drawing method
+     * ({@link #onDraw(android.graphics.Canvas)} for instance).</p>
+     * 
+     * @param width The width of the bitmap to create
+     * @param height The height of the bitmap to create
+     * 
+     * @return A valid {@link Bitmap.Config#ARGB_8888} bitmap, or null if the surface
+     *         texture is not available or width is &lt;= 0 or height is &lt;= 0
+     * 
+     * @see #isAvailable() 
+     * @see #getBitmap(android.graphics.Bitmap) 
+     * @see #getBitmap() 
+     */
+    public Bitmap getBitmap(int width, int height) {
+        if (isAvailable() && width > 0 && height > 0) {
+            return getBitmap(Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888));
+        }
+        return null;
+    }
+
+    /**
+     * <p>Copies the content of this view's surface texture into the specified
+     * bitmap. If the surface texture is not available, the copy is not executed.
+     * The content of the surface texture will be scaled to fit exactly inside
+     * the specified bitmap.</p>
+     * 
+     * <p><strong>Do not</strong> invoke this method from a drawing method
+     * ({@link #onDraw(android.graphics.Canvas)} for instance).</p>
+     * 
+     * @param bitmap The bitmap to copy the content of the surface texture into,
+     *               cannot be null, all configurations are supported
+     * 
+     * @return The bitmap specified as a parameter
+     * 
+     * @see #isAvailable() 
+     * @see #getBitmap(int, int)  
+     * @see #getBitmap() 
+     */
+    public Bitmap getBitmap(Bitmap bitmap) {
+        if (bitmap != null && isAvailable()) {
+            mAttachInfo.mHardwareRenderer.copyLayer(mLayer, bitmap);
+        }
+        return bitmap;
+    }
+
+    /**
+     * Returns true if the {@link SurfaceTexture} associated with this
+     * TextureView is available for rendering. When this method returns
+     * true, {@link #getSurfaceTexture()} returns a valid surface texture.
+     */
+    public boolean isAvailable() {
+        return mSurface != null;
+    }
+
+    /**
      * Returns the {@link SurfaceTexture} used by this view. This method
-     * may return null if the view is not attached to a window.
+     * may return null if the view is not attached to a window or if the surface
+     * texture has not been initialized yet.
+     * 
+     * @see #isAvailable() 
      */
     public SurfaceTexture getSurfaceTexture() {
         return mSurface;
diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp
index 57a97bd..7e82efb 100644
--- a/core/jni/android_view_GLES20Canvas.cpp
+++ b/core/jni/android_view_GLES20Canvas.cpp
@@ -667,6 +667,11 @@
     renderer->drawLayer(layer, x, y, paint);
 }
 
+static jboolean android_view_GLES20Canvas_copyLayer(JNIEnv* env, jobject clazz,
+        Layer* layer, SkBitmap* bitmap) {
+    return LayerRenderer::copyLayer(layer, bitmap);
+}
+
 #endif // USE_OPENGL_RENDERER
 
 // ----------------------------------------------------------------------------
@@ -792,6 +797,7 @@
     { "nDestroyLayer",           "(I)V",       (void*) android_view_GLES20Canvas_destroyLayer },
     { "nDestroyLayerDeferred",   "(I)V",       (void*) android_view_GLES20Canvas_destroyLayerDeferred },
     { "nDrawLayer",              "(IIFFI)V",   (void*) android_view_GLES20Canvas_drawLayer },
+    { "nCopyLayer",              "(II)Z",      (void*) android_view_GLES20Canvas_copyLayer },
 
 #endif
 };
diff --git a/libs/hwui/LayerRenderer.cpp b/libs/hwui/LayerRenderer.cpp
index f316ba7..146e789 100644
--- a/libs/hwui/LayerRenderer.cpp
+++ b/libs/hwui/LayerRenderer.cpp
@@ -315,5 +315,95 @@
     }
 }
 
+bool LayerRenderer::copyLayer(Layer* layer, SkBitmap* bitmap) {
+    Caches& caches = Caches::getInstance();
+    if (layer && layer->isTextureLayer && bitmap->width() <= caches.maxTextureSize &&
+            bitmap->height() <= caches.maxTextureSize) {
+
+        GLuint fbo = caches.fboCache.get();
+        if (!fbo) {
+            LOGW("Could not obtain an FBO");
+            return false;
+        }
+
+        GLuint texture;
+        GLuint previousFbo;
+
+        GLenum format;
+        GLenum type;
+
+        switch (bitmap->config()) {
+            case SkBitmap::kA8_Config:
+                format = GL_ALPHA;
+                type = GL_UNSIGNED_BYTE;
+                break;
+            case SkBitmap::kRGB_565_Config:
+                format = GL_RGB;
+                type = GL_UNSIGNED_SHORT_5_6_5;
+                break;
+            case SkBitmap::kARGB_4444_Config:
+                format = GL_RGBA;
+                type = GL_UNSIGNED_SHORT_4_4_4_4;
+                break;
+            case SkBitmap::kARGB_8888_Config:
+            default:
+                format = GL_RGBA;
+                type = GL_UNSIGNED_BYTE;
+                break;
+        }
+
+        glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*) &previousFbo);
+        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+
+        glGenTextures(1, &texture);
+
+        glActiveTexture(GL_TEXTURE0);
+        glBindTexture(GL_TEXTURE_2D, texture);
+
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+        glTexImage2D(GL_TEXTURE_2D, 0, format, bitmap->width(), bitmap->height(),
+                0, format, type, NULL);
+        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+                GL_TEXTURE_2D, texture, 0);
+
+        glBindTexture(GL_TEXTURE_2D, layer->texture);
+
+        float alpha = layer->alpha;
+        SkXfermode::Mode mode = layer->mode;
+
+        layer->mode = SkXfermode::kSrc_Mode;
+        layer->alpha = 255;
+        layer->fbo = fbo;
+
+        LayerRenderer renderer(layer);
+        renderer.setViewport(bitmap->width(), bitmap->height());
+        renderer.OpenGLRenderer::prepareDirty(0.0f, 0.0f,
+                bitmap->width(), bitmap->height(), !layer->blend);
+
+        Rect bounds;
+        bounds.set(0.0f, 0.0f, bitmap->width(), bitmap->height());
+        renderer.drawTextureLayer(layer, bounds);
+
+        SkAutoLockPixels alp(*bitmap);
+        glReadPixels(0, 0, bitmap->width(), bitmap->height(), format, type, bitmap->getPixels());
+
+        glBindFramebuffer(GL_FRAMEBUFFER, previousFbo);
+
+        layer->mode = mode;
+        layer->alpha = alpha;
+        layer->fbo = 0;
+        glDeleteTextures(1, &texture);
+        caches.fboCache.put(fbo);
+
+        return true;
+    }
+    return false;
+}
+
 }; // namespace uirenderer
 }; // namespace android
diff --git a/libs/hwui/LayerRenderer.h b/libs/hwui/LayerRenderer.h
index 59cab96..797dfc6 100644
--- a/libs/hwui/LayerRenderer.h
+++ b/libs/hwui/LayerRenderer.h
@@ -20,6 +20,8 @@
 #include "OpenGLRenderer.h"
 #include "Layer.h"
 
+#include <SkBitmap.h>
+
 namespace android {
 namespace uirenderer {
 
@@ -60,6 +62,7 @@
             GLenum renderTarget, float* transform);
     static void destroyLayer(Layer* layer);
     static void destroyLayerDeferred(Layer* layer);
+    static bool copyLayer(Layer* layer, SkBitmap* bitmap);
 
 private:
     void generateMesh();
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index b9e3ddc..0a3d5090 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -178,6 +178,14 @@
         return 0;
     }
 
+    /**
+     * Renders the specified layer as a textured quad.
+     *
+     * @param layer The layer to render
+     * @param rect The bounds of the layer
+     */
+    void drawTextureLayer(Layer* layer, const Rect& rect);
+
 private:
     /**
      * Saves the current state of the renderer as a new snapshot.
@@ -256,14 +264,6 @@
     void clearLayerRegions();
 
     /**
-     * Renders the specified layer as a textured quad.
-     *
-     * @param layer The layer to render
-     * @param rect The bounds of the layer
-     */
-    void drawTextureLayer(Layer* layer, const Rect& rect);
-
-    /**
      * Mark the layer as dirty at the specified coordinates. The coordinates
      * are transformed with the supplied matrix.
      */
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index 01d30eb..3e7ca08 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -19,6 +19,7 @@
 
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.CAMERA" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
 
     <uses-feature android:name="android.hardware.camera" />
     <uses-feature android:name="android.hardware.camera.autofocus" />