1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.test.hwui;
18
19import android.app.Activity;
20import android.graphics.Canvas;
21import android.graphics.ColorFilter;
22import android.graphics.Paint;
23import android.graphics.PixelFormat;
24import android.graphics.Rect;
25import android.graphics.drawable.Drawable;
26import android.os.Bundle;
27import android.view.DisplayListCanvas;
28import android.view.ThreadedRenderer;
29import android.view.RenderNode;
30import android.view.ThreadedRenderer;
31import android.view.View;
32import android.view.View.OnClickListener;
33import android.widget.AbsoluteLayout;
34import android.widget.AbsoluteLayout.LayoutParams;
35
36public class MultiProducerActivity extends Activity implements OnClickListener {
37    private static final int DURATION = 800;
38    private View mBackgroundTarget = null;
39    private View mFrameTarget = null;
40    private View mContent = null;
41    // The width & height of our "output drawing".
42    private final int WIDTH = 900;
43    private final int HEIGHT = 600;
44    // A border width around the drawing.
45    private static final int BORDER_WIDTH = 20;
46    // The Gap between the content and the frame which should get filled on the right and bottom
47    // side by the backdrop.
48    final int CONTENT_GAP = 100;
49
50    // For debug purposes - disable drawing of frame / background.
51    private final boolean USE_FRAME = true;
52    private final boolean USE_BACK = true;
53
54    @Override
55    protected void onCreate(Bundle savedInstanceState) {
56        super.onCreate(savedInstanceState);
57        // To make things simple - we do a quick and dirty absolute layout.
58        final AbsoluteLayout layout = new AbsoluteLayout(this);
59
60        // Create the outer frame
61        if (USE_FRAME) {
62            mFrameTarget = new View(this);
63            LayoutParams frameLP = new LayoutParams(WIDTH, HEIGHT, 0, 0);
64            layout.addView(mFrameTarget, frameLP);
65        }
66
67        // Create the background which fills the gap between content and frame.
68        if (USE_BACK) {
69            mBackgroundTarget = new View(this);
70            LayoutParams backgroundLP = new LayoutParams(
71                    WIDTH - 2 * BORDER_WIDTH, HEIGHT - 2 * BORDER_WIDTH,
72                    BORDER_WIDTH, BORDER_WIDTH);
73            layout.addView(mBackgroundTarget, backgroundLP);
74        }
75
76        // Create the content
77        // Note: We reduce the size by CONTENT_GAP pixels on right and bottom, so that they get
78        // drawn by the backdrop.
79        mContent = new View(this);
80        mContent.setBackground(new ColorPulse(0xFFF44336, 0xFF9C27B0, null));
81        mContent.setOnClickListener(this);
82        LayoutParams contentLP = new LayoutParams(WIDTH - 2 * BORDER_WIDTH - CONTENT_GAP,
83                HEIGHT - 2 * BORDER_WIDTH - CONTENT_GAP, BORDER_WIDTH, BORDER_WIDTH);
84        layout.addView(mContent, contentLP);
85
86        setContentView(layout);
87    }
88
89    @Override
90    protected void onStart() {
91        super.onStart();
92        View view = mBackgroundTarget != null ? mBackgroundTarget : mFrameTarget;
93        if (view != null) {
94            view.post(mSetup);
95        }
96    }
97
98    @Override
99    protected void onStop() {
100        super.onStop();
101        View view = mBackgroundTarget != null ? mBackgroundTarget : mFrameTarget;
102        if (view != null) {
103            view.removeCallbacks(mSetup);
104        }
105        if (mBgRenderer != null) {
106            mBgRenderer.destroy();
107            mBgRenderer = null;
108        }
109    }
110
111    @Override
112    public void onClick(View view) {
113        sBlockThread.run();
114    }
115
116    private Runnable mSetup = new Runnable() {
117        @Override
118        public void run() {
119            View view = mBackgroundTarget != null ? mBackgroundTarget : mFrameTarget;
120            if (view == null) {
121                view.postDelayed(mSetup, 50);
122            }
123            ThreadedRenderer renderer = view.getHardwareRenderer();
124            if (renderer == null || view.getWidth() == 0) {
125                view.postDelayed(mSetup, 50);
126            }
127            ThreadedRenderer threaded = (ThreadedRenderer) renderer;
128
129            mBgRenderer = new FakeFrame(threaded,mFrameTarget, mBackgroundTarget);
130            mBgRenderer.start();
131        }
132    };
133
134    private FakeFrame mBgRenderer;
135    private class FakeFrame extends Thread {
136        ThreadedRenderer mRenderer;
137        volatile boolean mRunning = true;
138        View mTargetFrame;
139        View mTargetBack;
140        Drawable mFrameContent;
141        Drawable mBackContent;
142        // The Z value where to place this.
143        int mZFrame;
144        int mZBack;
145        String mRenderNodeName;
146
147        FakeFrame(ThreadedRenderer renderer, View targetFrame, View targetBack) {
148            mRenderer = renderer;
149            mTargetFrame = targetFrame;
150
151            mTargetBack = targetBack;
152            mFrameContent = new ColorPulse(0xFF101010, 0xFF707070, new Rect(0, 0, WIDTH, HEIGHT));
153            mBackContent = new ColorPulse(0xFF909090, 0xFFe0e0e0, null);
154        }
155
156        @Override
157        public void run() {
158            Rect currentFrameBounds = new Rect();
159            Rect currentBackBounds = new Rect();
160            Rect newBounds = new Rect();
161            int[] surfaceOrigin = new int[2];
162            RenderNode nodeFrame = null;
163            RenderNode nodeBack = null;
164
165            // Since we are overriding the window painting logic we need to at least fill the
166            // surface with some window content (otherwise the world will go black).
167            try {
168                Thread.sleep(200);
169            } catch (InterruptedException e) {
170            }
171
172            if (mTargetBack != null) {
173                nodeBack = RenderNode.create("FakeBackdrop", null);
174                nodeBack.setClipToBounds(true);
175                mRenderer.addRenderNode(nodeBack, true);
176            }
177
178            if (mTargetFrame != null) {
179                nodeFrame = RenderNode.create("FakeFrame", null);
180                nodeFrame.setClipToBounds(true);
181                mRenderer.addRenderNode(nodeFrame, false);
182            }
183
184            while (mRunning) {
185                // Get the surface position to draw to within our surface.
186                surfaceOrigin[0] = 0;
187                surfaceOrigin[1] = 0;
188                // This call should be done while the rendernode's displaylist is produced.
189                // For simplicity of this test we do this before we kick off the draw.
190                mContent.getLocationInSurface(surfaceOrigin);
191                mRenderer.setContentDrawBounds(surfaceOrigin[0], surfaceOrigin[1],
192                        surfaceOrigin[0] + mContent.getWidth(),
193                        surfaceOrigin[1] + mContent.getHeight());
194                // Determine new position for frame.
195                if (nodeFrame != null) {
196                    surfaceOrigin[0] = 0;
197                    surfaceOrigin[1] = 0;
198                    mTargetFrame.getLocationInSurface(surfaceOrigin);
199                    newBounds.set(surfaceOrigin[0], surfaceOrigin[1],
200                            surfaceOrigin[0] + mTargetFrame.getWidth(),
201                            surfaceOrigin[1] + mTargetFrame.getHeight());
202                    if (!currentFrameBounds.equals(newBounds)) {
203                        currentFrameBounds.set(newBounds);
204                        nodeFrame.setLeftTopRightBottom(currentFrameBounds.left,
205                                currentFrameBounds.top,
206                                currentFrameBounds.right, currentFrameBounds.bottom);
207                    }
208
209                    // Draw frame
210                    DisplayListCanvas canvas = nodeFrame.start(currentFrameBounds.width(),
211                            currentFrameBounds.height());
212                    mFrameContent.draw(canvas);
213                    nodeFrame.end(canvas);
214                }
215
216                // Determine new position for backdrop
217                if (nodeBack != null) {
218                    surfaceOrigin[0] = 0;
219                    surfaceOrigin[1] = 0;
220                    mTargetBack.getLocationInSurface(surfaceOrigin);
221                    newBounds.set(surfaceOrigin[0], surfaceOrigin[1],
222                            surfaceOrigin[0] + mTargetBack.getWidth(),
223                            surfaceOrigin[1] + mTargetBack.getHeight());
224                    if (!currentBackBounds.equals(newBounds)) {
225                        currentBackBounds.set(newBounds);
226                        nodeBack.setLeftTopRightBottom(currentBackBounds.left,
227                                currentBackBounds.top,
228                                currentBackBounds.right, currentBackBounds.bottom);
229                    }
230
231                    // Draw Backdrop
232                    DisplayListCanvas canvas = nodeBack.start(currentBackBounds.width(),
233                            currentBackBounds.height());
234                    mBackContent.draw(canvas);
235                    nodeBack.end(canvas);
236                }
237
238                // we need to only render one guy - the rest will happen automatically (I think).
239                if (nodeFrame != null) {
240                    mRenderer.drawRenderNode(nodeFrame);
241                }
242                if (nodeBack != null) {
243                    mRenderer.drawRenderNode(nodeBack);
244                }
245                try {
246                    Thread.sleep(5);
247                } catch (InterruptedException e) {}
248            }
249            if (nodeFrame != null) {
250                mRenderer.removeRenderNode(nodeFrame);
251            }
252            if (nodeBack != null) {
253                mRenderer.removeRenderNode(nodeBack);
254            }
255        }
256
257        public void destroy() {
258            mRunning = false;
259            try {
260                join();
261            } catch (InterruptedException e) {}
262        }
263    }
264
265    private final static Runnable sBlockThread = new Runnable() {
266        @Override
267        public void run() {
268            try {
269                Thread.sleep(DURATION);
270            } catch (InterruptedException e) {
271            }
272        }
273    };
274
275    static class ColorPulse extends Drawable {
276
277        private int mColorStart;
278        private int mColorEnd;
279        private int mStep;
280        private Rect mRect;
281        private Paint mPaint = new Paint();
282
283        public ColorPulse(int color1, int color2, Rect rect) {
284            mColorStart = color1;
285            mColorEnd = color2;
286            if (rect != null) {
287                mRect = new Rect(rect.left + BORDER_WIDTH / 2, rect.top + BORDER_WIDTH / 2,
288                                 rect.right - BORDER_WIDTH / 2, rect.bottom - BORDER_WIDTH / 2);
289            }
290        }
291
292        static int evaluate(float fraction, int startInt, int endInt) {
293            int startA = (startInt >> 24) & 0xff;
294            int startR = (startInt >> 16) & 0xff;
295            int startG = (startInt >> 8) & 0xff;
296            int startB = startInt & 0xff;
297
298            int endA = (endInt >> 24) & 0xff;
299            int endR = (endInt >> 16) & 0xff;
300            int endG = (endInt >> 8) & 0xff;
301            int endB = endInt & 0xff;
302
303            return (int)((startA + (int)(fraction * (endA - startA))) << 24) |
304                    (int)((startR + (int)(fraction * (endR - startR))) << 16) |
305                    (int)((startG + (int)(fraction * (endG - startG))) << 8) |
306                    (int)((startB + (int)(fraction * (endB - startB))));
307        }
308
309        @Override
310        public void draw(Canvas canvas) {
311            float frac = mStep / 50.0f;
312            int color = evaluate(frac, mColorStart, mColorEnd);
313            if (mRect != null && !mRect.isEmpty()) {
314                mPaint.setStyle(Paint.Style.STROKE);
315                mPaint.setStrokeWidth(BORDER_WIDTH);
316                mPaint.setColor(color);
317                canvas.drawRect(mRect, mPaint);
318            } else {
319                canvas.drawColor(color);
320            }
321
322            mStep++;
323            if (mStep >= 50) {
324                mStep = 0;
325                int tmp = mColorStart;
326                mColorStart = mColorEnd;
327                mColorEnd = tmp;
328            }
329            invalidateSelf();
330        }
331
332        @Override
333        public void setAlpha(int alpha) {
334        }
335
336        @Override
337        public void setColorFilter(ColorFilter colorFilter) {
338        }
339
340        @Override
341        public int getOpacity() {
342            return mRect == null || mRect.isEmpty() ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT;
343        }
344
345    }
346}
347
348