/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.test.hwui; import android.app.Activity; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.view.DisplayListCanvas; import android.view.ThreadedRenderer; import android.view.RenderNode; import android.view.View; import android.view.View.OnClickListener; import android.widget.AbsoluteLayout; import android.widget.AbsoluteLayout.LayoutParams; public class MultiProducerActivity extends Activity implements OnClickListener { private static final int DURATION = 800; private View mBackgroundTarget = null; private View mFrameTarget = null; private View mContent = null; // The width & height of our "output drawing". private final int WIDTH = 900; private final int HEIGHT = 600; // A border width around the drawing. private static final int BORDER_WIDTH = 20; // The Gap between the content and the frame which should get filled on the right and bottom // side by the backdrop. final int CONTENT_GAP = 100; // For debug purposes - disable drawing of frame / background. private final boolean USE_FRAME = true; private final boolean USE_BACK = true; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // To make things simple - we do a quick and dirty absolute layout. final AbsoluteLayout layout = new AbsoluteLayout(this); // Create the outer frame if (USE_FRAME) { mFrameTarget = new View(this); LayoutParams frameLP = new LayoutParams(WIDTH, HEIGHT, 0, 0); layout.addView(mFrameTarget, frameLP); } // Create the background which fills the gap between content and frame. if (USE_BACK) { mBackgroundTarget = new View(this); LayoutParams backgroundLP = new LayoutParams( WIDTH - 2 * BORDER_WIDTH, HEIGHT - 2 * BORDER_WIDTH, BORDER_WIDTH, BORDER_WIDTH); layout.addView(mBackgroundTarget, backgroundLP); } // Create the content // Note: We reduce the size by CONTENT_GAP pixels on right and bottom, so that they get // drawn by the backdrop. mContent = new View(this); mContent.setBackground(new ColorPulse(0xFFF44336, 0xFF9C27B0, null)); mContent.setOnClickListener(this); LayoutParams contentLP = new LayoutParams(WIDTH - 2 * BORDER_WIDTH - CONTENT_GAP, HEIGHT - 2 * BORDER_WIDTH - CONTENT_GAP, BORDER_WIDTH, BORDER_WIDTH); layout.addView(mContent, contentLP); setContentView(layout); } @Override protected void onStart() { super.onStart(); View view = mBackgroundTarget != null ? mBackgroundTarget : mFrameTarget; if (view != null) { view.post(mSetup); } } @Override protected void onStop() { super.onStop(); View view = mBackgroundTarget != null ? mBackgroundTarget : mFrameTarget; if (view != null) { view.removeCallbacks(mSetup); } if (mBgRenderer != null) { mBgRenderer.destroy(); mBgRenderer = null; } } @Override public void onClick(View view) { sBlockThread.run(); } private Runnable mSetup = new Runnable() { @Override public void run() { View view = mBackgroundTarget != null ? mBackgroundTarget : mFrameTarget; if (view == null) { view.postDelayed(mSetup, 50); } ThreadedRenderer renderer = view.getThreadedRenderer(); if (renderer == null || view.getWidth() == 0) { view.postDelayed(mSetup, 50); } ThreadedRenderer threaded = (ThreadedRenderer) renderer; mBgRenderer = new FakeFrame(threaded,mFrameTarget, mBackgroundTarget); mBgRenderer.start(); } }; private FakeFrame mBgRenderer; private class FakeFrame extends Thread { ThreadedRenderer mRenderer; volatile boolean mRunning = true; View mTargetFrame; View mTargetBack; Drawable mFrameContent; Drawable mBackContent; // The Z value where to place this. int mZFrame; int mZBack; String mRenderNodeName; FakeFrame(ThreadedRenderer renderer, View targetFrame, View targetBack) { mRenderer = renderer; mTargetFrame = targetFrame; mTargetBack = targetBack; mFrameContent = new ColorPulse(0xFF101010, 0xFF707070, new Rect(0, 0, WIDTH, HEIGHT)); mBackContent = new ColorPulse(0xFF909090, 0xFFe0e0e0, null); } @Override public void run() { Rect currentFrameBounds = new Rect(); Rect currentBackBounds = new Rect(); Rect newBounds = new Rect(); int[] surfaceOrigin = new int[2]; RenderNode nodeFrame = null; RenderNode nodeBack = null; // Since we are overriding the window painting logic we need to at least fill the // surface with some window content (otherwise the world will go black). try { Thread.sleep(200); } catch (InterruptedException e) { } if (mTargetBack != null) { nodeBack = RenderNode.create("FakeBackdrop", null); nodeBack.setClipToBounds(true); mRenderer.addRenderNode(nodeBack, true); } if (mTargetFrame != null) { nodeFrame = RenderNode.create("FakeFrame", null); nodeFrame.setClipToBounds(true); mRenderer.addRenderNode(nodeFrame, false); } while (mRunning) { // Get the surface position to draw to within our surface. surfaceOrigin[0] = 0; surfaceOrigin[1] = 0; // This call should be done while the rendernode's displaylist is produced. // For simplicity of this test we do this before we kick off the draw. mContent.getLocationInSurface(surfaceOrigin); mRenderer.setContentDrawBounds(surfaceOrigin[0], surfaceOrigin[1], surfaceOrigin[0] + mContent.getWidth(), surfaceOrigin[1] + mContent.getHeight()); // Determine new position for frame. if (nodeFrame != null) { surfaceOrigin[0] = 0; surfaceOrigin[1] = 0; mTargetFrame.getLocationInSurface(surfaceOrigin); newBounds.set(surfaceOrigin[0], surfaceOrigin[1], surfaceOrigin[0] + mTargetFrame.getWidth(), surfaceOrigin[1] + mTargetFrame.getHeight()); if (!currentFrameBounds.equals(newBounds)) { currentFrameBounds.set(newBounds); nodeFrame.setLeftTopRightBottom(currentFrameBounds.left, currentFrameBounds.top, currentFrameBounds.right, currentFrameBounds.bottom); } // Draw frame DisplayListCanvas canvas = nodeFrame.start(currentFrameBounds.width(), currentFrameBounds.height()); mFrameContent.draw(canvas); nodeFrame.end(canvas); } // Determine new position for backdrop if (nodeBack != null) { surfaceOrigin[0] = 0; surfaceOrigin[1] = 0; mTargetBack.getLocationInSurface(surfaceOrigin); newBounds.set(surfaceOrigin[0], surfaceOrigin[1], surfaceOrigin[0] + mTargetBack.getWidth(), surfaceOrigin[1] + mTargetBack.getHeight()); if (!currentBackBounds.equals(newBounds)) { currentBackBounds.set(newBounds); nodeBack.setLeftTopRightBottom(currentBackBounds.left, currentBackBounds.top, currentBackBounds.right, currentBackBounds.bottom); } // Draw Backdrop DisplayListCanvas canvas = nodeBack.start(currentBackBounds.width(), currentBackBounds.height()); mBackContent.draw(canvas); nodeBack.end(canvas); } // we need to only render one guy - the rest will happen automatically (I think). if (nodeFrame != null) { mRenderer.drawRenderNode(nodeFrame); } if (nodeBack != null) { mRenderer.drawRenderNode(nodeBack); } try { Thread.sleep(5); } catch (InterruptedException e) {} } if (nodeFrame != null) { mRenderer.removeRenderNode(nodeFrame); } if (nodeBack != null) { mRenderer.removeRenderNode(nodeBack); } } public void destroy() { mRunning = false; try { join(); } catch (InterruptedException e) {} } } private final static Runnable sBlockThread = new Runnable() { @Override public void run() { try { Thread.sleep(DURATION); } catch (InterruptedException e) { } } }; static class ColorPulse extends Drawable { private int mColorStart; private int mColorEnd; private int mStep; private Rect mRect; private Paint mPaint = new Paint(); public ColorPulse(int color1, int color2, Rect rect) { mColorStart = color1; mColorEnd = color2; if (rect != null) { mRect = new Rect(rect.left + BORDER_WIDTH / 2, rect.top + BORDER_WIDTH / 2, rect.right - BORDER_WIDTH / 2, rect.bottom - BORDER_WIDTH / 2); } } static int evaluate(float fraction, int startInt, int endInt) { int startA = (startInt >> 24) & 0xff; int startR = (startInt >> 16) & 0xff; int startG = (startInt >> 8) & 0xff; int startB = startInt & 0xff; int endA = (endInt >> 24) & 0xff; int endR = (endInt >> 16) & 0xff; int endG = (endInt >> 8) & 0xff; int endB = endInt & 0xff; return (int)((startA + (int)(fraction * (endA - startA))) << 24) | (int)((startR + (int)(fraction * (endR - startR))) << 16) | (int)((startG + (int)(fraction * (endG - startG))) << 8) | (int)((startB + (int)(fraction * (endB - startB)))); } @Override public void draw(Canvas canvas) { float frac = mStep / 50.0f; int color = evaluate(frac, mColorStart, mColorEnd); if (mRect != null && !mRect.isEmpty()) { mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth(BORDER_WIDTH); mPaint.setColor(color); canvas.drawRect(mRect, mPaint); } else { canvas.drawColor(color); } mStep++; if (mStep >= 50) { mStep = 0; int tmp = mColorStart; mColorStart = mColorEnd; mColorEnd = tmp; } invalidateSelf(); } @Override public void setAlpha(int alpha) { } @Override public void setColorFilter(ColorFilter colorFilter) { } @Override public int getOpacity() { return mRect == null || mRect.isEmpty() ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT; } } }