Surface:lockHardwareCanvas

Bug: 17440886

Change-Id: I1f2d98c63ec1a2814c2258cf7e0096139263770a
diff --git a/api/current.txt b/api/current.txt
index caf28cb..9fe71d4 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -33369,6 +33369,7 @@
     method public int describeContents();
     method public boolean isValid();
     method public android.graphics.Canvas lockCanvas(android.graphics.Rect) throws java.lang.IllegalArgumentException, android.view.Surface.OutOfResourcesException;
+    method public android.graphics.Canvas lockHardwareCanvas();
     method public void readFromParcel(android.os.Parcel);
     method public void release();
     method public deprecated void unlockCanvas(android.graphics.Canvas);
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index 3770b8a..132e25c 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -87,6 +87,8 @@
     // non compatibility mode.
     private Matrix mCompatibleMatrix;
 
+    private HwuiContext mHwuiContext;
+
     /** @hide */
     @IntDef({ROTATION_0, ROTATION_90, ROTATION_180, ROTATION_270})
     @Retention(RetentionPolicy.SOURCE)
@@ -171,6 +173,10 @@
                 nativeRelease(mNativeObject);
                 setNativeObjectLocked(0);
             }
+            if (mHwuiContext != null) {
+                mHwuiContext.destroy();
+                mHwuiContext = null;
+            }
         }
     }
 
@@ -264,27 +270,60 @@
      * @param canvas The canvas previously obtained from {@link #lockCanvas}.
      */
     public void unlockCanvasAndPost(Canvas canvas) {
+        synchronized (mLock) {
+            checkNotReleasedLocked();
+
+            if (mHwuiContext != null) {
+                mHwuiContext.unlockAndPost(canvas);
+            } else {
+                unlockSwCanvasAndPost(canvas);
+            }
+        }
+    }
+
+    private void unlockSwCanvasAndPost(Canvas canvas) {
         if (canvas != mCanvas) {
             throw new IllegalArgumentException("canvas object must be the same instance that "
                     + "was previously returned by lockCanvas");
         }
+        if (mNativeObject != mLockedObject) {
+            Log.w(TAG, "WARNING: Surface's mNativeObject (0x" +
+                    Long.toHexString(mNativeObject) + ") != mLockedObject (0x" +
+                    Long.toHexString(mLockedObject) +")");
+        }
+        if (mLockedObject == 0) {
+            throw new IllegalStateException("Surface was not locked");
+        }
+        try {
+            nativeUnlockCanvasAndPost(mLockedObject, canvas);
+        } finally {
+            nativeRelease(mLockedObject);
+            mLockedObject = 0;
+        }
+    }
 
+    /**
+     * Gets a {@link Canvas} for drawing into this surface.
+     *
+     * After drawing into the provided {@link Canvas}, the caller must
+     * invoke {@link #unlockCanvasAndPost} to post the new contents to the surface.
+     *
+     * Unlike {@link #lockCanvas(Rect)} this will return a hardware-accelerated
+     * canvas. See the <a href="{@docRoot}guide/topics/graphics/hardware-accel.html#unsupported">
+     * unsupported drawing operations</a> for a list of what is and isn't
+     * supported in a hardware-accelerated canvas.
+     *
+     * @return A canvas for drawing into the surface.
+     *
+     * @throws IllegalStateException If the canvas cannot be locked.
+     */
+    public Canvas lockHardwareCanvas() {
         synchronized (mLock) {
             checkNotReleasedLocked();
-            if (mNativeObject != mLockedObject) {
-                Log.w(TAG, "WARNING: Surface's mNativeObject (0x" +
-                        Long.toHexString(mNativeObject) + ") != mLockedObject (0x" +
-                        Long.toHexString(mLockedObject) +")");
+            if (mHwuiContext == null) {
+                mHwuiContext = new HwuiContext();
             }
-            if (mLockedObject == 0) {
-                throw new IllegalStateException("Surface was not locked");
-            }
-            try {
-                nativeUnlockCanvasAndPost(mLockedObject, canvas);
-            } finally {
-                nativeRelease(mLockedObject);
-                mLockedObject = 0;
-            }
+            return mHwuiContext.lockCanvas();
         }
     }
 
@@ -415,6 +454,9 @@
             }
             mNativeObject = ptr;
             mGenerationId += 1;
+            if (mHwuiContext != null) {
+                mHwuiContext.updateSurface();
+            }
         }
     }
 
@@ -518,4 +560,59 @@
             mOrigMatrix.set(m);
         }
     }
+
+    private final class HwuiContext {
+        private final RenderNode mRenderNode;
+        private long mHwuiRenderer;
+        private HardwareCanvas mCanvas;
+
+        HwuiContext() {
+            mRenderNode = RenderNode.create("HwuiCanvas", null);
+            mRenderNode.setClipToBounds(false);
+            mHwuiRenderer = nHwuiCreate(mRenderNode.mNativeRenderNode, mNativeObject);
+        }
+
+        Canvas lockCanvas() {
+            if (mCanvas != null) {
+                throw new IllegalStateException("Surface was already locked!");
+            }
+            mCanvas = mRenderNode.start(0, 0);
+            return mCanvas;
+        }
+
+        void unlockAndPost(Canvas canvas) {
+            if (canvas != mCanvas) {
+                throw new IllegalArgumentException("canvas object must be the same instance that "
+                        + "was previously returned by lockCanvas");
+            }
+            mRenderNode.end(mCanvas);
+            mCanvas = null;
+            nHwuiDraw(mHwuiRenderer);
+        }
+
+        void updateSurface() {
+            nHwuiSetSurface(mHwuiRenderer, mNativeObject);
+        }
+
+        void destroy() {
+            if (mHwuiRenderer != 0) {
+                nHwuiDestroy(mHwuiRenderer);
+                mHwuiRenderer = 0;
+            }
+        }
+
+        @Override
+        protected void finalize() throws Throwable {
+            try {
+                destroy();
+            } finally {
+                super.finalize();
+            }
+        }
+    }
+
+    private static native long nHwuiCreate(long rootNode, long surface);
+    private static native void nHwuiSetSurface(long renderer, long surface);
+    private static native void nHwuiDraw(long renderer);
+    private static native void nHwuiDestroy(long renderer);
 }
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index 9bb8195..b3d9890 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -47,6 +47,11 @@
 
 #include <ScopedUtfChars.h>
 
+#include <AnimationContext.h>
+#include <DisplayListRenderer.h>
+#include <RenderNode.h>
+#include <renderthread/RenderProxy.h>
+
 // ----------------------------------------------------------------------------
 
 namespace android {
@@ -352,8 +357,53 @@
     parcel->writeStrongBinder( self != 0 ? self->getIGraphicBufferProducer()->asBinder() : NULL);
 }
 
+namespace uirenderer {
+
+using namespace android::uirenderer::renderthread;
+
+class ContextFactory : public IContextFactory {
+public:
+    virtual AnimationContext* createAnimationContext(renderthread::TimeLord& clock) {
+        return new AnimationContext(clock);
+    }
+};
+
+static jlong create(JNIEnv* env, jclass clazz, jlong rootNodePtr, jlong surfacePtr) {
+    RenderNode* rootNode = reinterpret_cast<RenderNode*>(rootNodePtr);
+    sp<Surface> surface(reinterpret_cast<Surface*>(surfacePtr));
+    ContextFactory factory;
+    RenderProxy* proxy = new RenderProxy(false, rootNode, &factory);
+    proxy->loadSystemProperties();
+    proxy->initialize(surface);
+    // Shadows can't be used via this interface, so just set the light source
+    // to all 0s. (and width & height are unused, TODO remove them)
+    proxy->setup(0, 0, (Vector3){0, 0, 0}, 0, 0, 0);
+    return (jlong) proxy;
+}
+
+static void setSurface(JNIEnv* env, jclass clazz, jlong rendererPtr, jlong surfacePtr) {
+    RenderProxy* proxy = reinterpret_cast<RenderProxy*>(rendererPtr);
+    sp<Surface> surface(reinterpret_cast<Surface*>(surfacePtr));
+    proxy->updateSurface(surface);
+}
+
+static void draw(JNIEnv* env, jclass clazz, jlong rendererPtr) {
+    RenderProxy* proxy = reinterpret_cast<RenderProxy*>(rendererPtr);
+    nsecs_t frameTimeNs = systemTime(CLOCK_MONOTONIC);
+    proxy->syncAndDrawFrame(frameTimeNs, 0, 1.0f);
+}
+
+static void destroy(JNIEnv* env, jclass clazz, jlong rendererPtr) {
+    RenderProxy* proxy = reinterpret_cast<RenderProxy*>(rendererPtr);
+    delete proxy;
+}
+
+} // uirenderer
+
 // ----------------------------------------------------------------------------
 
+namespace hwui = android::uirenderer;
+
 static JNINativeMethod gSurfaceMethods[] = {
     {"nativeCreateFromSurfaceTexture", "(Landroid/graphics/SurfaceTexture;)J",
             (void*)nativeCreateFromSurfaceTexture },
@@ -375,6 +425,12 @@
             (void*)nativeReadFromParcel },
     {"nativeWriteToParcel", "(JLandroid/os/Parcel;)V",
             (void*)nativeWriteToParcel },
+
+    // HWUI context
+    {"nHwuiCreate", "(JJ)J", (void*) hwui::create },
+    {"nHwuiSetSurface", "(JJ)V", (void*) hwui::setSurface },
+    {"nHwuiDraw", "(J)V", (void*) hwui::draw },
+    {"nHwuiDestroy", "(J)V", (void*) hwui::destroy },
 };
 
 int register_android_view_Surface(JNIEnv* env)
diff --git a/libs/hwui/renderthread/TimeLord.cpp b/libs/hwui/renderthread/TimeLord.cpp
index cf3d039..9bd4eae 100644
--- a/libs/hwui/renderthread/TimeLord.cpp
+++ b/libs/hwui/renderthread/TimeLord.cpp
@@ -20,7 +20,7 @@
 namespace renderthread {
 
 TimeLord::TimeLord()
-        : mFrameIntervalNanos(0)
+        : mFrameIntervalNanos(milliseconds_to_nanoseconds(16))
         , mFrameTimeNanos(0) {
 }
 
diff --git a/libs/hwui/tests/main.cpp b/libs/hwui/tests/main.cpp
index 3e9a86f..2b80d56 100644
--- a/libs/hwui/tests/main.cpp
+++ b/libs/hwui/tests/main.cpp
@@ -81,8 +81,8 @@
     rootNode->mutateStagingProperties().setClipToBounds(false);
     rootNode->setPropertyFieldsDirty(RenderNode::GENERIC);
 
-    RenderProxy* proxy = new RenderProxy(false, rootNode, new ContextFactory());
-    proxy->setFrameInterval(milliseconds_to_nanoseconds(16));
+    ContextFactory factory;
+    RenderProxy* proxy = new RenderProxy(false, rootNode, &factory);
     proxy->loadSystemProperties();
     proxy->initialize(surface);
     float lightX = width / 2.0;
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index bc2f1fd..8531944 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -342,6 +342,24 @@
         </activity>
 
         <activity
+                android:name="HardwareCanvasTextureViewActivity"
+                android:label="TextureView/HardwareCanvas">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="com.android.test.hwui.TEST" />
+            </intent-filter>
+        </activity>
+
+        <activity
+                android:name="HardwareCanvasSurfaceViewActivity"
+                android:label="SurfaceView/HardwareCanvas">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="com.android.test.hwui.TEST" />
+            </intent-filter>
+        </activity>
+
+        <activity
                 android:name="GLTextureViewActivity"
                 android:label="TextureView/OpenGL">
             <intent-filter>
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/HardwareCanvasSurfaceViewActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/HardwareCanvasSurfaceViewActivity.java
new file mode 100644
index 0000000..b1431c5
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/HardwareCanvasSurfaceViewActivity.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2014 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.app.Activity;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.os.Bundle;
+import android.view.Gravity;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceHolder.Callback;
+import android.view.SurfaceView;
+import android.widget.FrameLayout;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class HardwareCanvasSurfaceViewActivity extends Activity implements Callback {
+    private SurfaceView mSurfaceView;
+    private HardwareCanvasSurfaceViewActivity.RenderingThread mThread;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        FrameLayout content = new FrameLayout(this);
+
+        mSurfaceView = new SurfaceView(this);
+        mSurfaceView.getHolder().addCallback(this);
+
+        content.addView(mSurfaceView, new FrameLayout.LayoutParams(
+                FrameLayout.LayoutParams.MATCH_PARENT,
+                FrameLayout.LayoutParams.MATCH_PARENT,
+                Gravity.CENTER));
+        setContentView(content);
+    }
+
+    @Override
+    public void surfaceCreated(SurfaceHolder holder) {
+        mThread = new RenderingThread(holder.getSurface());
+        mThread.start();
+    }
+
+    @Override
+    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+        mThread.setSize(width, height);
+    }
+
+    @Override
+    public void surfaceDestroyed(SurfaceHolder holder) {
+        if (mThread != null) mThread.stopRendering();
+    }
+
+    private static class RenderingThread extends Thread {
+        private final Surface mSurface;
+        private volatile boolean mRunning = true;
+        private int mWidth, mHeight;
+
+        public RenderingThread(Surface surface) {
+            mSurface = surface;
+        }
+
+        void setSize(int width, int height) {
+            mWidth = width;
+            mHeight = height;
+        }
+
+        @Override
+        public void run() {
+            float x = 0.0f;
+            float y = 0.0f;
+            float speedX = 5.0f;
+            float speedY = 3.0f;
+
+            Paint paint = new Paint();
+            paint.setColor(0xff00ff00);
+
+            while (mRunning && !Thread.interrupted()) {
+                final Canvas canvas = mSurface.lockHardwareCanvas();
+                try {
+                    canvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR);
+                    canvas.drawRect(x, y, x + 20.0f, y + 20.0f, paint);
+                } finally {
+                    mSurface.unlockCanvasAndPost(canvas);
+                }
+
+                if (x + 20.0f + speedX >= mWidth || x + speedX <= 0.0f) {
+                    speedX = -speedX;
+                }
+                if (y + 20.0f + speedY >= mHeight || y + speedY <= 0.0f) {
+                    speedY = -speedY;
+                }
+
+                x += speedX;
+                y += speedY;
+
+                try {
+                    Thread.sleep(15);
+                } catch (InterruptedException e) {
+                    // Interrupted
+                }
+            }
+        }
+
+        void stopRendering() {
+            interrupt();
+            mRunning = false;
+        }
+    }
+}
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/HardwareCanvasTextureViewActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/HardwareCanvasTextureViewActivity.java
new file mode 100644
index 0000000..63a6efa
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/HardwareCanvasTextureViewActivity.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2014 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.app.Activity;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.SurfaceTexture;
+import android.os.Bundle;
+import android.view.Gravity;
+import android.view.Surface;
+import android.view.TextureView;
+import android.widget.FrameLayout;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class HardwareCanvasTextureViewActivity extends Activity
+        implements TextureView.SurfaceTextureListener {
+    private TextureView mTextureView;
+    private HardwareCanvasTextureViewActivity.RenderingThread mThread;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        FrameLayout content = new FrameLayout(this);
+
+        mTextureView = new TextureView(this);
+        mTextureView.setSurfaceTextureListener(this);
+        mTextureView.setOpaque(false);
+
+        content.addView(mTextureView, new FrameLayout.LayoutParams(500, 500, Gravity.CENTER));
+        setContentView(content);
+    }
+
+    @Override
+    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
+        mThread = new RenderingThread(mTextureView);
+        mThread.start();
+    }
+
+    @Override
+    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
+        // Ignored
+    }
+
+    @Override
+    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
+        if (mThread != null) mThread.stopRendering();
+        return true;
+    }
+
+    @Override
+    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
+        // Ignored
+    }
+
+    private static class RenderingThread extends Thread {
+        private final TextureView mView;
+        private final Surface mSurface;
+        private volatile boolean mRunning = true;
+
+        public RenderingThread(TextureView view) {
+            mView = view;
+            mSurface = new Surface(mView.getSurfaceTexture());
+        }
+
+        @Override
+        public void run() {
+            float x = 0.0f;
+            float y = 0.0f;
+            float speedX = 5.0f;
+            float speedY = 3.0f;
+
+            Paint paint = new Paint();
+            paint.setColor(0xff00ff00);
+
+            while (mRunning && !Thread.interrupted()) {
+                final Canvas canvas = mSurface.lockHardwareCanvas();
+                try {
+                    canvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR);
+                    canvas.drawRect(x, y, x + 20.0f, y + 20.0f, paint);
+                } finally {
+                    mSurface.unlockCanvasAndPost(canvas);
+                }
+
+                if (x + 20.0f + speedX >= mView.getWidth() || x + speedX <= 0.0f) {
+                    speedX = -speedX;
+                }
+                if (y + 20.0f + speedY >= mView.getHeight() || y + speedY <= 0.0f) {
+                    speedY = -speedY;
+                }
+
+                x += speedX;
+                y += speedY;
+
+                try {
+                    Thread.sleep(15);
+                } catch (InterruptedException e) {
+                    // Interrupted
+                }
+            }
+        }
+
+        void stopRendering() {
+            interrupt();
+            mRunning = false;
+        }
+    }
+}