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