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