private Choreographer mChoreographer;
private boolean mRootNodeNeedsUpdate;
+ // In case of multi threaded render nodes, these bounds indicate the content bounds against
+ // which the backdrop needs to be cropped against.
+ private final Rect mCurrentContentBounds = new Rect();
+ private final Rect mStagedContentBounds = new Rect();
+
ThreadedRenderer(Context context, boolean translucent) {
final TypedArray a = context.obtainStyledAttributes(null, R.styleable.Lighting, 0, 0);
mLightY = a.getDimension(R.styleable.Lighting_lightY, 0);
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
+ /**
+ * Adds a rendernode to the renderer which can be drawn and changed asynchronously to the
+ * rendernode of the UI thread.
+ * @param node The node to add.
+ * @param placeFront If true, the render node will be placed in front of the content node,
+ * otherwise behind the content node.
+ */
+ public void addRenderNode(RenderNode node, boolean placeFront) {
+ nAddRenderNode(mNativeProxy, node.mNativeRenderNode, placeFront);
+ }
+
+ /**
+ * Only especially added render nodes can be removed.
+ * @param node The node which was added via addRenderNode which should get removed again.
+ */
+ public void removeRenderNode(RenderNode node) {
+ nRemoveRenderNode(mNativeProxy, node.mNativeRenderNode);
+ }
+
+ /**
+ * Draws a particular render node. If the node is not the content node, only the additional
+ * nodes will get drawn and the content remains untouched.
+ * @param node The node to be drawn.
+ */
+ public void drawRenderNode(RenderNode node) {
+ nDrawRenderNode(mNativeProxy, node.mNativeRenderNode);
+ }
+
+ /**
+ * To avoid unnecessary overdrawing of the main content all additionally passed render nodes
+ * will be prevented to overdraw this area. It will be synchronized with the draw call.
+ * This should be updated in the content view's draw call.
+ * @param left The left side of the protected bounds.
+ * @param top The top side of the protected bounds.
+ * @param right The right side of the protected bounds.
+ * @param bottom The bottom side of the protected bounds.
+ */
+ public void setContentOverdrawProtectionBounds(int left, int top, int right, int bottom) {
+ mStagedContentBounds.set(left, top, right, bottom);
+ }
+
@Override
void invalidateRoot() {
mRootNodeNeedsUpdate = true;
choreographer.mFrameInfo.markDrawStart();
updateRootDisplayList(view, callbacks);
+ // The main content view was updating the content bounds and we transfer them to the
+ // renderer.
+ if (!mCurrentContentBounds.equals(mStagedContentBounds)) {
+ mCurrentContentBounds.set(mStagedContentBounds);
+ nSetContentOverdrawProtectionBounds(mNativeProxy, mCurrentContentBounds.left,
+ mCurrentContentBounds.top, mCurrentContentBounds.right,
+ mCurrentContentBounds.bottom);
+ }
attachInfo.mIgnoreDirtyState = false;
private static native void nDumpProfileInfo(long nativeProxy, FileDescriptor fd,
@DumpFlags int dumpFlags);
private static native void nDumpProfileData(byte[] data, FileDescriptor fd);
+
+ private static native void nAddRenderNode(long nativeProxy, long rootRenderNode,
+ boolean placeFront);
+ private static native void nRemoveRenderNode(long nativeProxy, long rootRenderNode);
+ private static native void nDrawRenderNode(long nativeProxy, long rootRenderNode);
+ private static native void nSetContentOverdrawProtectionBounds(long nativeProxy, int left,
+ int top, int right, int bottom);
}
}
/**
+ * Compute the view's coordinate within the surface.
+ *
+ * <p>Computes the coordinates of this view in its surface. The argument
+ * must be an array of two integers. After the method returns, the array
+ * contains the x and y location in that order.</p>
+ * @hide
+ * @param location an array of two integers in which to hold the coordinates
+ */
+ public void getLocationInSurface(@Size(2) int[] location) {
+ getLocationInWindow(location);
+ if (mAttachInfo != null && mAttachInfo.mViewRootImpl != null) {
+ location[0] += mAttachInfo.mViewRootImpl.mWindowAttributes.surfaceInsets.left;
+ location[1] += mAttachInfo.mViewRootImpl.mWindowAttributes.surfaceInsets.top;
+ }
+ }
+
+ /**
* Provide original WindowInsets that are dispatched to the view hierarchy. The insets are
* only available if the view is attached.
*
}
}
+static void android_view_ThreadedRenderer_addRenderNode(JNIEnv* env, jobject clazz,
+ jlong proxyPtr, jlong renderNodePtr, jboolean placeFront) {
+ RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
+ RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
+ proxy->addRenderNode(renderNode, placeFront);
+}
+
+static void android_view_ThreadedRenderer_removeRenderNode(JNIEnv* env, jobject clazz,
+ jlong proxyPtr, jlong renderNodePtr) {
+ RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
+ RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
+ proxy->removeRenderNode(renderNode);
+}
+
+static void android_view_ThreadedRendererd_drawRenderNode(JNIEnv* env, jobject clazz,
+ jlong proxyPtr, jlong renderNodePtr) {
+ RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
+ RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
+ proxy->drawRenderNode(renderNode);
+}
+
+static void android_view_ThreadedRenderer_setContentOverdrawProtectionBounds(JNIEnv* env,
+ jobject clazz, jlong proxyPtr, jint left, jint top, jint right, jint bottom) {
+ RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
+ proxy->setContentOverdrawProtectionBounds(left, top, right, bottom);
+}
// ----------------------------------------------------------------------------
// Shaders
static void android_view_ThreadedRenderer_setupShadersDiskCache(JNIEnv* env, jobject clazz,
jstring diskCachePath) {
-
const char* cacheArray = env->GetStringUTFChars(diskCachePath, NULL);
egl_cache_t::get()->setCacheFilename(cacheArray);
env->ReleaseStringUTFChars(diskCachePath, cacheArray);
{ "nDumpProfileData", "([BLjava/io/FileDescriptor;)V", (void*) android_view_ThreadedRenderer_dumpProfileData },
{ "setupShadersDiskCache", "(Ljava/lang/String;)V",
(void*) android_view_ThreadedRenderer_setupShadersDiskCache },
+ { "nAddRenderNode", "(JJZ)V", (void*) android_view_ThreadedRenderer_addRenderNode},
+ { "nRemoveRenderNode", "(JJ)V", (void*) android_view_ThreadedRenderer_removeRenderNode},
+ { "nDrawRenderNode", "(JJ)V", (void*) android_view_ThreadedRendererd_drawRenderNode},
+ { "nSetContentOverdrawProtectionBounds", "(JIIII)V",
+ (void*)android_view_ThreadedRenderer_setContentOverdrawProtectionBounds},
};
int register_android_view_ThreadedRenderer(JNIEnv* env) {
, canvasContext(clone.canvasContext)
{}
- const TraversalMode mode;
+ TraversalMode mode;
// TODO: Remove this? Currently this is used to signal to stop preparing
// textures if we run out of cache space.
bool prepareTextures;
, mEglManager(thread.eglManager())
, mOpaque(!translucent)
, mAnimationContext(contextFactory->createAnimationContext(mRenderThread.timeLord()))
- , mRootRenderNode(rootRenderNode)
, mJankTracker(thread.timeLord().frameIntervalNanos())
- , mProfiler(mFrames) {
+ , mProfiler(mFrames)
+ , mContentOverdrawProtectionBounds(0, 0, 0, 0) {
+ mRenderNodes.emplace_back(rootRenderNode);
mRenderThread.renderState().registerCanvasContext(this);
mProfiler.setDensity(mRenderThread.mainDisplayInfo().density);
}
return info && ((*info)[FrameInfoIndex::Flags] & FrameInfoFlags::SkippedFrame);
}
-void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t syncQueued) {
+void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo,
+ int64_t syncQueued, RenderNode* target) {
mRenderThread.removeFrameCallback(this);
// If the previous frame was dropped we don't need to hold onto it, so
info.canvasContext = this;
mAnimationContext->startFrame(info.mode);
- mRootRenderNode->prepareTree(info);
+ for (const sp<RenderNode>& node : mRenderNodes) {
+ // Only the primary target node will be drawn full - all other nodes would get drawn in
+ // real time mode. In case of a window, the primary node is the window content and the other
+ // node(s) are non client / filler nodes.
+ info.mode = (node.get() == target ? TreeInfo::MODE_FULL : TreeInfo::MODE_RT_ONLY);
+ node->prepareTree(info);
+ }
mAnimationContext->runRemainingAnimations(info);
freePrefetechedLayers();
dirty.fLeft, dirty.fTop, dirty.fRight, dirty.fBottom, mOpaque);
Rect outBounds;
- mCanvas->drawRenderNode(mRootRenderNode.get(), outBounds);
+ // It there are multiple render nodes, they are as follows:
+ // #0 - backdrop
+ // #1 - content (with - and clipped to - bounds mContentOverdrawProtectionBounds)
+ // #2 - frame
+ // Usually the backdrop cannot be seen since it will be entirely covered by the content. While
+ // resizing however it might become partially visible. The following render loop will crop the
+ // backdrop against the content and draw the remaining part of it. It will then crop the content
+ // against the backdrop (since that indicates a shrinking of the window) and then the frame
+ // around everything.
+ // The bounds of the backdrop against which the content should be clipped.
+ Rect backdropBounds = mContentOverdrawProtectionBounds;
+ // If there is no content bounds we ignore the layering as stated above and start with 2.
+ int layer = mContentOverdrawProtectionBounds.isEmpty() ? 2 : 0;
+ // Draw all render nodes. Note that
+ for (const sp<RenderNode>& node : mRenderNodes) {
+ if (layer == 0) { // Backdrop.
+ // Draw the backdrop clipped to the inverse content bounds.
+ const RenderProperties& properties = node->properties();
+ Rect targetBounds(properties.getLeft(), properties.getTop(),
+ properties.getRight(), properties.getBottom());
+ // Remember the intersection of the target bounds and the intersection bounds against
+ // which we have to crop the content.
+ backdropBounds.intersect(targetBounds);
+ // Check if we have to draw something on the left side ...
+ if (targetBounds.left < mContentOverdrawProtectionBounds.left) {
+ mCanvas->save(SkCanvas::kClip_SaveFlag);
+ if (mCanvas->clipRect(targetBounds.left, targetBounds.top,
+ mContentOverdrawProtectionBounds.left, targetBounds.bottom,
+ SkRegion::kIntersect_Op)) {
+ mCanvas->drawRenderNode(node.get(), outBounds);
+ }
+ // Reduce the target area by the area we have just painted.
+ targetBounds.left = std::min(mContentOverdrawProtectionBounds.left,
+ targetBounds.right);
+ mCanvas->restore();
+ }
+ // ... or on the right side ...
+ if (targetBounds.right > mContentOverdrawProtectionBounds.right &&
+ !targetBounds.isEmpty()) {
+ mCanvas->save(SkCanvas::kClip_SaveFlag);
+ if (mCanvas->clipRect(mContentOverdrawProtectionBounds.right, targetBounds.top,
+ targetBounds.right, targetBounds.bottom,
+ SkRegion::kIntersect_Op)) {
+ mCanvas->drawRenderNode(node.get(), outBounds);
+ }
+ // Reduce the target area by the area we have just painted.
+ targetBounds.right = std::max(targetBounds.left,
+ mContentOverdrawProtectionBounds.right);
+ mCanvas->restore();
+ }
+ // ... or at the top ...
+ if (targetBounds.top < mContentOverdrawProtectionBounds.top &&
+ !targetBounds.isEmpty()) {
+ mCanvas->save(SkCanvas::kClip_SaveFlag);
+ if (mCanvas->clipRect(targetBounds.left, targetBounds.top, targetBounds.right,
+ mContentOverdrawProtectionBounds.top,
+ SkRegion::kIntersect_Op)) {
+ mCanvas->drawRenderNode(node.get(), outBounds);
+ }
+ // Reduce the target area by the area we have just painted.
+ targetBounds.top = std::min(mContentOverdrawProtectionBounds.top,
+ targetBounds.bottom);
+ mCanvas->restore();
+ }
+ // ... or at the bottom.
+ if (targetBounds.bottom > mContentOverdrawProtectionBounds.bottom &&
+ !targetBounds.isEmpty()) {
+ mCanvas->save(SkCanvas::kClip_SaveFlag);
+ if (mCanvas->clipRect(targetBounds.left,
+ mContentOverdrawProtectionBounds.bottom, targetBounds.right,
+ targetBounds.bottom, SkRegion::kIntersect_Op)) {
+ mCanvas->drawRenderNode(node.get(), outBounds);
+ }
+ mCanvas->restore();
+ }
+ } else if (layer == 1) { // Content
+ // It gets cropped against the bounds of the backdrop to stay inside.
+ mCanvas->save(SkCanvas::kClip_SaveFlag);
+ if (mCanvas->clipRect(backdropBounds.left, backdropBounds.top,
+ backdropBounds.right, backdropBounds.bottom,
+ SkRegion::kIntersect_Op)) {
+ mCanvas->drawRenderNode(node.get(), outBounds);
+ }
+ mCanvas->restore();
+ } else { // draw the rest on top at will!
+ mCanvas->drawRenderNode(node.get(), outBounds);
+ }
+ layer++;
+ }
profiler().draw(mCanvas);
if (CC_UNLIKELY(!mCanvas || mEglSurface == EGL_NO_SURFACE)) {
return;
}
+ prepareAndDraw(nullptr);
+}
+void CanvasContext::prepareAndDraw(RenderNode* node) {
ATRACE_CALL();
int64_t frameInfo[UI_THREAD_FRAME_INFO_SIZE];
mRenderThread.timeLord().latestVsync());
TreeInfo info(TreeInfo::MODE_RT_ONLY, mRenderThread.renderState());
- prepareTree(info, frameInfo, systemTime(CLOCK_MONOTONIC));
+ prepareTree(info, frameInfo, systemTime(CLOCK_MONOTONIC), node);
if (info.out.canDrawThisFrame) {
draw();
}
stopDrawing();
if (mEglManager.hasEglContext()) {
freePrefetechedLayers();
- mRootRenderNode->destroyHardwareResources();
+ for (const sp<RenderNode>& node : mRenderNodes) {
+ node->destroyHardwareResources();
+ }
Caches& caches = Caches::getInstance();
// Make sure to release all the textures we were owning as there won't
// be another draw
#include <set>
#include <string>
+#include <vector>
namespace android {
namespace uirenderer {
void setOpaque(bool opaque);
void makeCurrent();
void processLayerUpdate(DeferredLayerUpdater* layerUpdater);
- void prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t syncQueued);
+ void prepareTree(TreeInfo& info, int64_t* uiFrameInfo,
+ int64_t syncQueued, RenderNode* target);
void draw();
void destroy();
// IFrameCallback, Chroreographer-driven frame callback entry point
virtual void doFrame() override;
+ void prepareAndDraw(RenderNode* node);
void buildLayer(RenderNode* node);
bool copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap);
void serializeDisplayListTree();
+ void addRenderNode(RenderNode* node, bool placeFront) {
+ int pos = placeFront ? 0 : static_cast<int>(mRenderNodes.size());
+ mRenderNodes.emplace( mRenderNodes.begin() + pos, node);
+ }
+
+ void removeRenderNode(RenderNode* node) {
+ mRenderNodes.erase(std::remove(mRenderNodes.begin(), mRenderNodes.end(), node),
+ mRenderNodes.end());
+ }
+
+ void setContentOverdrawProtectionBounds(int left, int top, int right, int bottom) {
+ mContentOverdrawProtectionBounds.set(left, top, right, bottom);
+ }
+
private:
friend class RegisterFrameCallbackTask;
// TODO: Replace with something better for layer & other GL object
DamageAccumulator mDamageAccumulator;
std::unique_ptr<AnimationContext> mAnimationContext;
- const sp<RenderNode> mRootRenderNode;
+ std::vector< sp<RenderNode> > mRenderNodes;
FrameInfo* mCurrentFrameInfo = nullptr;
// Ring buffer large enough for 2 seconds worth of frames
FrameInfoVisualizer mProfiler;
std::set<RenderNode*> mPrefetechedLayers;
+
+ // Stores the bounds of the main content.
+ Rect mContentOverdrawProtectionBounds;
};
} /* namespace renderthread */
DrawFrameTask::~DrawFrameTask() {
}
-void DrawFrameTask::setContext(RenderThread* thread, CanvasContext* context) {
+void DrawFrameTask::setContext(RenderThread* thread, CanvasContext* context,
+ RenderNode* targetNode) {
mRenderThread = thread;
mContext = context;
+ mTargetNode = targetNode;
}
void DrawFrameTask::pushLayerUpdate(DeferredLayerUpdater* layer) {
mContext->processLayerUpdate(mLayers[i].get());
}
mLayers.clear();
- mContext->prepareTree(info, mFrameInfo, mSyncQueued);
+ mContext->prepareTree(info, mFrameInfo, mSyncQueued, mTargetNode);
// This is after the prepareTree so that any pending operations
// (RenderNode tree state, prefetched layers, etc...) will be flushed.
DrawFrameTask();
virtual ~DrawFrameTask();
- void setContext(RenderThread* thread, CanvasContext* context);
+ void setContext(RenderThread* thread, CanvasContext* context, RenderNode* targetNode);
void pushLayerUpdate(DeferredLayerUpdater* layer);
void removeLayerUpdate(DeferredLayerUpdater* layer);
RenderThread* mRenderThread;
CanvasContext* mContext;
+ RenderNode* mTargetNode = nullptr;
/*********************************************
* Single frame data
args->thread = &mRenderThread;
args->contextFactory = contextFactory;
mContext = (CanvasContext*) postAndWait(task);
- mDrawFrameTask.setContext(&mRenderThread, mContext);
+ mDrawFrameTask.setContext(&mRenderThread, mContext, rootRenderNode);
}
RenderProxy::~RenderProxy() {
SETUP_TASK(destroyContext);
args->context = mContext;
mContext = nullptr;
- mDrawFrameTask.setContext(nullptr, nullptr);
+ mDrawFrameTask.setContext(nullptr, nullptr, nullptr);
// This is also a fence as we need to be certain that there are no
// outstanding mDrawFrame tasks posted before it is destroyed
postAndWait(task);
staticPostAndWait(task);
}
-CREATE_BRIDGE4(setTextureAtlas, RenderThread* thread, GraphicBuffer* buffer, int64_t* map, size_t size) {
+CREATE_BRIDGE4(setTextureAtlas, RenderThread* thread, GraphicBuffer* buffer, int64_t* map,
+ size_t size) {
CanvasContext::setTextureAtlas(*args->thread, args->buffer, args->map, args->size);
args->buffer->decStrong(nullptr);
return nullptr;
post(task);
}
+CREATE_BRIDGE3(addRenderNode, CanvasContext* context, RenderNode* node, bool placeFront) {
+ args->context->addRenderNode(args->node, args->placeFront);
+ return nullptr;
+}
+
+void RenderProxy::addRenderNode(RenderNode* node, bool placeFront) {
+ SETUP_TASK(addRenderNode);
+ args->context = mContext;
+ args->node = node;
+ args->placeFront = placeFront;
+ post(task);
+}
+
+CREATE_BRIDGE2(removeRenderNode, CanvasContext* context, RenderNode* node) {
+ args->context->removeRenderNode(args->node);
+ return nullptr;
+}
+
+void RenderProxy::removeRenderNode(RenderNode* node) {
+ SETUP_TASK(removeRenderNode);
+ args->context = mContext;
+ args->node = node;
+ post(task);
+}
+
+CREATE_BRIDGE2(drawRenderNode, CanvasContext* context, RenderNode* node) {
+ args->context->prepareAndDraw(args->node);
+ return nullptr;
+}
+
+void RenderProxy::drawRenderNode(RenderNode* node) {
+ SETUP_TASK(drawRenderNode);
+ args->context = mContext;
+ args->node = node;
+ // Be pseudo-thread-safe and don't use any member variables
+ staticPostAndWait(task);
+}
+
+CREATE_BRIDGE5(setContentOverdrawProtectionBounds, CanvasContext* context, int left, int top,
+ int right, int bottom) {
+ args->context->setContentOverdrawProtectionBounds(args->left, args->top, args->right,
+ args->bottom);
+ return nullptr;
+}
+
+void RenderProxy::setContentOverdrawProtectionBounds(int left, int top, int right, int bottom) {
+ SETUP_TASK(setContentOverdrawProtectionBounds);
+ args->context = mContext;
+ args->left = left;
+ args->top = top;
+ args->right = right;
+ args->bottom = bottom;
+ staticPostAndWait(task);
+}
+
CREATE_BRIDGE1(serializeDisplayListTree, CanvasContext* context) {
args->context->serializeDisplayListTree();
return nullptr;
ANDROID_API void serializeDisplayListTree();
+ ANDROID_API void addRenderNode(RenderNode* node, bool placeFront);
+ ANDROID_API void removeRenderNode(RenderNode* node);
+ ANDROID_API void drawRenderNode(RenderNode* node);
+ ANDROID_API void setContentOverdrawProtectionBounds(int left, int top, int right, int bottom);
+
private:
RenderThread& mRenderThread;
CanvasContext* mContext;
</intent-filter>
</activity>
+ <activity
+ android:name="MultiProducerActivity"
+ android:label="Threads/Multiple Producers">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="com.android.test.hwui.TEST" />
+ </intent-filter>
+ </activity>
+
</application>
</manifest>
--- /dev/null
+/*
+ * 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.HardwareRenderer;
+import android.view.RenderNode;
+import android.view.ThreadedRenderer;
+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);
+ }
+ HardwareRenderer renderer = view.getHardwareRenderer();
+ 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.setContentOverdrawProtectionBounds(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;
+ }
+
+ }
+}
+