MultiThreaded rendering of different renderNodes

This is adding the renderer side infrastructure to allow
rendering multiple render nodes with different threads.
This is a pre-step for decoupling a non client decor
resize reder from a content resize render.

Multiple render nodes can be added to be drawn, and to
prevent overdrawing, a content bounds area can be set

Bug: 22527834

Change-Id: Ie7271e20895bf38957e5a84aeefc883e282039ad
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/MultiProducerActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/MultiProducerActivity.java
new file mode 100644
index 0000000..b458d9b
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/MultiProducerActivity.java
@@ -0,0 +1,347 @@
+/*
+ * 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.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.view.DisplayListCanvas;
+import android.view.HardwareRenderer;
+import android.view.RenderNode;
+import android.view.ThreadedRenderer;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.AbsoluteLayout;
+import android.widget.AbsoluteLayout.LayoutParams;
+
+public class MultiProducerActivity extends Activity implements OnClickListener {
+    private static final int DURATION = 800;
+    private View mBackgroundTarget = null;
+    private View mFrameTarget = null;
+    private View mContent = null;
+    // The width & height of our "output drawing".
+    private final int WIDTH = 900;
+    private final int HEIGHT = 600;
+    // A border width around the drawing.
+    private static final int BORDER_WIDTH = 20;
+    // The Gap between the content and the frame which should get filled on the right and bottom
+    // side by the backdrop.
+    final int CONTENT_GAP = 100;
+
+    // For debug purposes - disable drawing of frame / background.
+    private final boolean USE_FRAME = true;
+    private final boolean USE_BACK = true;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        // To make things simple - we do a quick and dirty absolute layout.
+        final AbsoluteLayout layout = new AbsoluteLayout(this);
+
+        // Create the outer frame
+        if (USE_FRAME) {
+            mFrameTarget = new View(this);
+            LayoutParams frameLP = new LayoutParams(WIDTH, HEIGHT, 0, 0);
+            layout.addView(mFrameTarget, frameLP);
+        }
+
+        // Create the background which fills the gap between content and frame.
+        if (USE_BACK) {
+            mBackgroundTarget = new View(this);
+            LayoutParams backgroundLP = new LayoutParams(
+                    WIDTH - 2 * BORDER_WIDTH, HEIGHT - 2 * BORDER_WIDTH,
+                    BORDER_WIDTH, BORDER_WIDTH);
+            layout.addView(mBackgroundTarget, backgroundLP);
+        }
+
+        // Create the content
+        // Note: We reduce the size by CONTENT_GAP pixels on right and bottom, so that they get
+        // drawn by the backdrop.
+        mContent = new View(this);
+        mContent.setBackground(new ColorPulse(0xFFF44336, 0xFF9C27B0, null));
+        mContent.setOnClickListener(this);
+        LayoutParams contentLP = new LayoutParams(WIDTH - 2 * BORDER_WIDTH - CONTENT_GAP,
+                HEIGHT - 2 * BORDER_WIDTH - CONTENT_GAP, BORDER_WIDTH, BORDER_WIDTH);
+        layout.addView(mContent, contentLP);
+
+        setContentView(layout);
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        View view = mBackgroundTarget != null ? mBackgroundTarget : mFrameTarget;
+        if (view != null) {
+            view.post(mSetup);
+        }
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        View view = mBackgroundTarget != null ? mBackgroundTarget : mFrameTarget;
+        if (view != null) {
+            view.removeCallbacks(mSetup);
+        }
+        if (mBgRenderer != null) {
+            mBgRenderer.destroy();
+            mBgRenderer = null;
+        }
+    }
+
+    @Override
+    public void onClick(View view) {
+        sBlockThread.run();
+    }
+
+    private Runnable mSetup = new Runnable() {
+        @Override
+        public void run() {
+            View view = mBackgroundTarget != null ? mBackgroundTarget : mFrameTarget;
+            if (view == null) {
+                view.postDelayed(mSetup, 50);
+            }
+            HardwareRenderer renderer = view.getHardwareRenderer();
+            if (renderer == null || view.getWidth() == 0) {
+                view.postDelayed(mSetup, 50);
+            }
+            ThreadedRenderer threaded = (ThreadedRenderer) renderer;
+
+            mBgRenderer = new FakeFrame(threaded,mFrameTarget, mBackgroundTarget);
+            mBgRenderer.start();
+        }
+    };
+
+    private FakeFrame mBgRenderer;
+    private class FakeFrame extends Thread {
+        ThreadedRenderer mRenderer;
+        volatile boolean mRunning = true;
+        View mTargetFrame;
+        View mTargetBack;
+        Drawable mFrameContent;
+        Drawable mBackContent;
+        // The Z value where to place this.
+        int mZFrame;
+        int mZBack;
+        String mRenderNodeName;
+
+        FakeFrame(ThreadedRenderer renderer, View targetFrame, View targetBack) {
+            mRenderer = renderer;
+            mTargetFrame = targetFrame;
+
+            mTargetBack = targetBack;
+            mFrameContent = new ColorPulse(0xFF101010, 0xFF707070, new Rect(0, 0, WIDTH, HEIGHT));
+            mBackContent = new ColorPulse(0xFF909090, 0xFFe0e0e0, null);
+        }
+
+        @Override
+        public void run() {
+            Rect currentFrameBounds = new Rect();
+            Rect currentBackBounds = new Rect();
+            Rect newBounds = new Rect();
+            int[] surfaceOrigin = new int[2];
+            RenderNode nodeFrame = null;
+            RenderNode nodeBack = null;
+
+            // Since we are overriding the window painting logic we need to at least fill the
+            // surface with some window content (otherwise the world will go black).
+            try {
+                Thread.sleep(200);
+            } catch (InterruptedException e) {
+            }
+
+            if (mTargetBack != null) {
+                nodeBack = RenderNode.create("FakeBackdrop", null);
+                nodeBack.setClipToBounds(true);
+                mRenderer.addRenderNode(nodeBack, true);
+            }
+
+            if (mTargetFrame != null) {
+                nodeFrame = RenderNode.create("FakeFrame", null);
+                nodeFrame.setClipToBounds(true);
+                mRenderer.addRenderNode(nodeFrame, false);
+            }
+
+            while (mRunning) {
+                // Get the surface position to draw to within our surface.
+                surfaceOrigin[0] = 0;
+                surfaceOrigin[1] = 0;
+                // This call should be done while the rendernode's displaylist is produced.
+                // For simplicity of this test we do this before we kick off the draw.
+                mContent.getLocationInSurface(surfaceOrigin);
+                mRenderer.setContentOverdrawProtectionBounds(surfaceOrigin[0], surfaceOrigin[1],
+                        surfaceOrigin[0] + mContent.getWidth(),
+                        surfaceOrigin[1] + mContent.getHeight());
+                // Determine new position for frame.
+                if (nodeFrame != null) {
+                    surfaceOrigin[0] = 0;
+                    surfaceOrigin[1] = 0;
+                    mTargetFrame.getLocationInSurface(surfaceOrigin);
+                    newBounds.set(surfaceOrigin[0], surfaceOrigin[1],
+                            surfaceOrigin[0] + mTargetFrame.getWidth(),
+                            surfaceOrigin[1] + mTargetFrame.getHeight());
+                    if (!currentFrameBounds.equals(newBounds)) {
+                        currentFrameBounds.set(newBounds);
+                        nodeFrame.setLeftTopRightBottom(currentFrameBounds.left,
+                                currentFrameBounds.top,
+                                currentFrameBounds.right, currentFrameBounds.bottom);
+                    }
+
+                    // Draw frame
+                    DisplayListCanvas canvas = nodeFrame.start(currentFrameBounds.width(),
+                            currentFrameBounds.height());
+                    mFrameContent.draw(canvas);
+                    nodeFrame.end(canvas);
+                }
+
+                // Determine new position for backdrop
+                if (nodeBack != null) {
+                    surfaceOrigin[0] = 0;
+                    surfaceOrigin[1] = 0;
+                    mTargetBack.getLocationInSurface(surfaceOrigin);
+                    newBounds.set(surfaceOrigin[0], surfaceOrigin[1],
+                            surfaceOrigin[0] + mTargetBack.getWidth(),
+                            surfaceOrigin[1] + mTargetBack.getHeight());
+                    if (!currentBackBounds.equals(newBounds)) {
+                        currentBackBounds.set(newBounds);
+                        nodeBack.setLeftTopRightBottom(currentBackBounds.left,
+                                currentBackBounds.top,
+                                currentBackBounds.right, currentBackBounds.bottom);
+                    }
+
+                    // Draw Backdrop
+                    DisplayListCanvas canvas = nodeBack.start(currentBackBounds.width(),
+                            currentBackBounds.height());
+                    mBackContent.draw(canvas);
+                    nodeBack.end(canvas);
+                }
+
+                // we need to only render one guy - the rest will happen automatically (I think).
+                if (nodeFrame != null) {
+                    mRenderer.drawRenderNode(nodeFrame);
+                }
+                if (nodeBack != null) {
+                    mRenderer.drawRenderNode(nodeBack);
+                }
+                try {
+                    Thread.sleep(5);
+                } catch (InterruptedException e) {}
+            }
+            if (nodeFrame != null) {
+                mRenderer.removeRenderNode(nodeFrame);
+            }
+            if (nodeBack != null) {
+                mRenderer.removeRenderNode(nodeBack);
+            }
+        }
+
+        public void destroy() {
+            mRunning = false;
+            try {
+                join();
+            } catch (InterruptedException e) {}
+        }
+    }
+
+    private final static Runnable sBlockThread = new Runnable() {
+        @Override
+        public void run() {
+            try {
+                Thread.sleep(DURATION);
+            } catch (InterruptedException e) {
+            }
+        }
+    };
+
+    static class ColorPulse extends Drawable {
+
+        private int mColorStart;
+        private int mColorEnd;
+        private int mStep;
+        private Rect mRect;
+        private Paint mPaint = new Paint();
+
+        public ColorPulse(int color1, int color2, Rect rect) {
+            mColorStart = color1;
+            mColorEnd = color2;
+            if (rect != null) {
+                mRect = new Rect(rect.left + BORDER_WIDTH / 2, rect.top + BORDER_WIDTH / 2,
+                                 rect.right - BORDER_WIDTH / 2, rect.bottom - BORDER_WIDTH / 2);
+            }
+        }
+
+        static int evaluate(float fraction, int startInt, int endInt) {
+            int startA = (startInt >> 24) & 0xff;
+            int startR = (startInt >> 16) & 0xff;
+            int startG = (startInt >> 8) & 0xff;
+            int startB = startInt & 0xff;
+
+            int endA = (endInt >> 24) & 0xff;
+            int endR = (endInt >> 16) & 0xff;
+            int endG = (endInt >> 8) & 0xff;
+            int endB = endInt & 0xff;
+
+            return (int)((startA + (int)(fraction * (endA - startA))) << 24) |
+                    (int)((startR + (int)(fraction * (endR - startR))) << 16) |
+                    (int)((startG + (int)(fraction * (endG - startG))) << 8) |
+                    (int)((startB + (int)(fraction * (endB - startB))));
+        }
+
+        @Override
+        public void draw(Canvas canvas) {
+            float frac = mStep / 50.0f;
+            int color = evaluate(frac, mColorStart, mColorEnd);
+            if (mRect != null && !mRect.isEmpty()) {
+                mPaint.setStyle(Paint.Style.STROKE);
+                mPaint.setStrokeWidth(BORDER_WIDTH);
+                mPaint.setColor(color);
+                canvas.drawRect(mRect, mPaint);
+            } else {
+                canvas.drawColor(color);
+            }
+
+            mStep++;
+            if (mStep >= 50) {
+                mStep = 0;
+                int tmp = mColorStart;
+                mColorStart = mColorEnd;
+                mColorEnd = tmp;
+            }
+            invalidateSelf();
+        }
+
+        @Override
+        public void setAlpha(int alpha) {
+        }
+
+        @Override
+        public void setColorFilter(ColorFilter colorFilter) {
+        }
+
+        @Override
+        public int getOpacity() {
+            return mRect == null || mRect.isEmpty() ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT;
+        }
+
+    }
+}
+