| /* |
| * 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; |
| } |
| |
| } |
| } |
| |