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