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 
17 package com.android.test.hwui;
18 
19 import android.app.Activity;
20 import android.graphics.Canvas;
21 import android.graphics.ColorFilter;
22 import android.graphics.Paint;
23 import android.graphics.PixelFormat;
24 import android.graphics.RecordingCanvas;
25 import android.graphics.Rect;
26 import android.graphics.drawable.Drawable;
27 import android.os.Bundle;
28 import android.view.ThreadedRenderer;
29 import android.graphics.RenderNode;
30 import android.view.View;
31 import android.view.View.OnClickListener;
32 import android.widget.AbsoluteLayout;
33 import android.widget.AbsoluteLayout.LayoutParams;
34 
35 public 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
onCreate(Bundle savedInstanceState)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
onStart()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
onStop()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
onClick(View view)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 
FakeFrame(ThreadedRenderer renderer, View targetFrame, View targetBack)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
run()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                     RecordingCanvas 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                     RecordingCanvas 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 
destroy()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 
ColorPulse(int color1, int color2, Rect rect)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 
evaluate(float fraction, int startInt, int endInt)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
draw(Canvas canvas)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
setAlpha(int alpha)332         public void setAlpha(int alpha) {
333         }
334 
335         @Override
setColorFilter(ColorFilter colorFilter)336         public void setColorFilter(ColorFilter colorFilter) {
337         }
338 
339         @Override
getOpacity()340         public int getOpacity() {
341             return mRect == null || mRect.isEmpty() ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT;
342         }
343 
344     }
345 }
346 
347