Moved BackdropFrameRenderer from NonClientDecorView to its own class file

Bug: 24810450
Change-Id: Id86e97733161499bbc59617433792f5ddc4e7f9e
This commit is contained in:
Wale Ogunwale
2015-11-17 11:26:56 -08:00
parent 0d7e912b00
commit 0b3562db3e
2 changed files with 308 additions and 275 deletions

View File

@@ -0,0 +1,289 @@
/*
* Copyright (C) 2015 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.internal.policy;
import com.android.internal.widget.NonClientDecorView;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Looper;
import android.view.Choreographer;
import android.view.DisplayListCanvas;
import android.view.RenderNode;
import android.view.ThreadedRenderer;
import android.view.View;
/**
* The thread which draws a fill in background while the app is resizing in areas where the app
* content draw is lagging behind the resize operation.
* It starts with the creation and it ends once someone calls destroy().
* Any size changes can be passed by a call to setTargetRect will passed to the thread and
* executed via the Choreographer.
* @hide
*/
public class BackdropFrameRenderer extends Thread implements Choreographer.FrameCallback {
private NonClientDecorView mNonClientDecorView;
// This is containing the last requested size by a resize command. Note that this size might
// or might not have been applied to the output already.
private final Rect mTargetRect = new Rect();
// The render nodes for the multi threaded renderer.
private ThreadedRenderer mRenderer;
private RenderNode mFrameAndBackdropNode;
private final Rect mOldTargetRect = new Rect();
private final Rect mNewTargetRect = new Rect();
private Choreographer mChoreographer;
// Cached size values from the last render for the case that the view hierarchy is gone
// during a configuration change.
private int mLastContentWidth;
private int mLastContentHeight;
private int mLastCaptionHeight;
private int mLastXOffset;
private int mLastYOffset;
// Whether to report when next frame is drawn or not.
private boolean mReportNextDraw;
public BackdropFrameRenderer(NonClientDecorView nonClientDecorView,
ThreadedRenderer renderer,
Rect initialBounds) {
mNonClientDecorView = nonClientDecorView;
setName("ResizeFrame");
mRenderer = renderer;
// Create a render node for the content and frame backdrop
// which can be resized independently from the content.
mFrameAndBackdropNode = RenderNode.create("FrameAndBackdropNode", null);
mRenderer.addRenderNode(mFrameAndBackdropNode, true);
// Set the initial bounds and draw once so that we do not get a broken frame.
mTargetRect.set(initialBounds);
synchronized (this) {
changeWindowSizeLocked(initialBounds);
}
// Kick off our draw thread.
start();
}
/**
* Call this function asynchronously when the window size has been changed. The change will
* be picked up once per frame and the frame will be re-rendered accordingly.
* @param newTargetBounds The new target bounds.
*/
public void setTargetRect(Rect newTargetBounds) {
synchronized (this) {
mTargetRect.set(newTargetBounds);
// Notify of a bounds change.
pingRenderLocked();
}
}
/**
* The window got replaced due to a configuration change.
*/
public void onConfigurationChange() {
synchronized (this) {
if (mRenderer != null) {
// Enforce a window redraw.
mOldTargetRect.set(0, 0, 0, 0);
pingRenderLocked();
}
}
}
/**
* All resources of the renderer will be released. This function can be called from the
* the UI thread as well as the renderer thread.
*/
public void releaseRenderer() {
synchronized (this) {
if (mRenderer != null) {
// Invalidate the current content bounds.
mRenderer.setContentDrawBounds(0, 0, 0, 0);
// Remove the render node again
// (see comment above - better to do that only once).
mRenderer.removeRenderNode(mFrameAndBackdropNode);
mRenderer = null;
// Exit the renderer loop.
pingRenderLocked();
}
}
}
@Override
public void run() {
try {
Looper.prepare();
synchronized (this) {
mChoreographer = Choreographer.getInstance();
// Draw at least once.
mChoreographer.postFrameCallback(this);
}
Looper.loop();
} finally {
releaseRenderer();
}
synchronized (this) {
// Make sure no more messages are being sent.
mChoreographer = null;
}
}
/**
* The implementation of the FrameCallback.
* @param frameTimeNanos The time in nanoseconds when the frame started being rendered,
* in the {@link System#nanoTime()} timebase. Divide this value by {@code 1000000}
*/
@Override
public void doFrame(long frameTimeNanos) {
synchronized (this) {
if (mRenderer == null) {
reportDrawIfNeeded();
// Tell the looper to stop. We are done.
Looper.myLooper().quit();
return;
}
mNewTargetRect.set(mTargetRect);
if (!mNewTargetRect.equals(mOldTargetRect) || mReportNextDraw) {
mOldTargetRect.set(mNewTargetRect);
changeWindowSizeLocked(mNewTargetRect);
}
}
}
/**
* The content is about to be drawn and we got the location of where it will be shown.
* If a "changeWindowSizeLocked" call has already been processed, we will re-issue the call
* if the previous call was ignored since the size was unknown.
* @param xOffset The x offset where the content is drawn to.
* @param yOffset The y offset where the content is drawn to.
* @param xSize The width size of the content. This should not be 0.
* @param ySize The height of the content.
* @return true if a frame should be requested after the content is drawn; false otherwise.
*/
public boolean onContentDrawn(int xOffset, int yOffset, int xSize, int ySize) {
synchronized (this) {
final boolean firstCall = mLastContentWidth == 0;
// The current content buffer is drawn here.
mLastContentWidth = xSize;
mLastContentHeight = ySize - mLastCaptionHeight;
mLastXOffset = xOffset;
mLastYOffset = yOffset;
mRenderer.setContentDrawBounds(
mLastXOffset,
mLastYOffset,
mLastXOffset + mLastContentWidth,
mLastYOffset + mLastCaptionHeight + mLastContentHeight);
// If this was the first call and changeWindowSizeLocked got already called prior
// to us, we should re-issue a changeWindowSizeLocked now.
return firstCall && (mLastCaptionHeight != 0 || !mNonClientDecorView.mShowDecor);
}
}
public void onRequestDraw(boolean reportNextDraw) {
synchronized (this) {
mReportNextDraw = reportNextDraw;
mOldTargetRect.set(0, 0, 0, 0);
pingRenderLocked();
}
}
/**
* Resizing the frame to fit the new window size.
* @param newBounds The window bounds which needs to be drawn.
*/
private void changeWindowSizeLocked(Rect newBounds) {
// While a configuration change is taking place the view hierarchy might become
// inaccessible. For that case we remember the previous metrics to avoid flashes.
// Note that even when there is no visible caption, the caption child will exist.
View caption = mNonClientDecorView.getChildAt(0);
if (caption != null) {
final int captionHeight = caption.getHeight();
// The caption height will probably never dynamically change while we are resizing.
// Once set to something other then 0 it should be kept that way.
if (captionHeight != 0) {
// Remember the height of the caption.
mLastCaptionHeight = captionHeight;
}
}
// Make sure that the other thread has already prepared the render draw calls for the
// content. If any size is 0, we have to wait for it to be drawn first.
if ((mLastCaptionHeight == 0 && mNonClientDecorView.mShowDecor) ||
mLastContentWidth == 0 || mLastContentHeight == 0) {
return;
}
// Since the surface is spanning the entire screen, we have to add the start offset of
// the bounds to get to the surface location.
final int left = mLastXOffset + newBounds.left;
final int top = mLastYOffset + newBounds.top;
final int width = newBounds.width();
final int height = newBounds.height();
mFrameAndBackdropNode.setLeftTopRightBottom(left, top, left + width, top + height);
// Draw the caption and content backdrops in to our render node.
DisplayListCanvas canvas = mFrameAndBackdropNode.start(width, height);
mNonClientDecorView.mCaptionBackgroundDrawable.setBounds(
0, 0, left + width, top + mLastCaptionHeight);
mNonClientDecorView.mCaptionBackgroundDrawable.draw(canvas);
// The backdrop: clear everything with the background. Clipping is done elsewhere.
mNonClientDecorView.mResizingBackgroundDrawable.setBounds(
0, mLastCaptionHeight, left + width, top + height);
mNonClientDecorView.mResizingBackgroundDrawable.draw(canvas);
mFrameAndBackdropNode.end(canvas);
// We need to render the node explicitly
mRenderer.drawRenderNode(mFrameAndBackdropNode);
reportDrawIfNeeded();
}
/**
* Notify view root that a frame has been drawn by us, if it has requested so.
*/
private void reportDrawIfNeeded() {
if (mReportNextDraw) {
if (mNonClientDecorView.isAttachedToWindow()) {
mNonClientDecorView.getViewRootImpl().reportDrawFinish();
}
mReportNextDraw = false;
}
}
/**
* Sends a message to the renderer to wake up and perform the next action which can be
* either the next rendering or the self destruction if mRenderer is null.
* Note: This call must be synchronized.
*/
private void pingRenderLocked() {
if (mChoreographer != null) {
mChoreographer.postFrameCallback(this);
}
}
}

View File

@@ -21,13 +21,9 @@ import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
import android.content.Context;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Looper;
import android.os.RemoteException;
import android.util.AttributeSet;
import android.view.Choreographer;
import android.view.DisplayListCanvas;
import android.view.MotionEvent;
import android.view.RenderNode;
import android.view.ThreadedRenderer;
import android.view.View;
import android.widget.LinearLayout;
@@ -39,6 +35,7 @@ import android.util.Log;
import android.util.TypedValue;
import com.android.internal.R;
import com.android.internal.policy.BackdropFrameRenderer;
import com.android.internal.policy.PhoneWindow;
/**
@@ -75,7 +72,7 @@ public class NonClientDecorView extends LinearLayout
private final int DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP = 5;
private PhoneWindow mOwner = null;
private boolean mWindowHasShadow = false;
private boolean mShowDecor = false;
public boolean mShowDecor = false;
// True when this object is listening for window size changes.
private boolean mAttachedCallbacksToRootViewImpl = false;
@@ -96,11 +93,10 @@ public class NonClientDecorView extends LinearLayout
// to max until the first layout command has been executed.
private boolean mAllowUpdateElevation = false;
// The resize frame renderer.
private ResizeFrameThread mFrameRendererThread = null;
private BackdropFrameRenderer mBackdropFrameRenderer = null;
private Drawable mResizingBackgroundDrawable;
private Drawable mCaptionBackgroundDrawable;
public Drawable mResizingBackgroundDrawable;
public Drawable mCaptionBackgroundDrawable;
public NonClientDecorView(Context context) {
super(context);
@@ -122,10 +118,10 @@ public class NonClientDecorView extends LinearLayout
// Note that our ViewRootImpl object will not change.
getViewRootImpl().addWindowCallbacks(this);
mAttachedCallbacksToRootViewImpl = true;
} else if (mFrameRendererThread != null) {
} else if (mBackdropFrameRenderer != null) {
// We are resizing and this call happened due to a configuration change. Tell the
// renderer about it.
mFrameRendererThread.onConfigurationChange();
mBackdropFrameRenderer.onConfigurationChange();
}
}
@@ -298,7 +294,7 @@ public class NonClientDecorView extends LinearLayout
float elevation = 0;
// Do not use a shadow when we are in resizing mode (mRenderer not null) since the shadow
// is bound to the content size and not the target size.
if (mWindowHasShadow && mFrameRendererThread == null) {
if (mWindowHasShadow && mBackdropFrameRenderer == null) {
boolean fill = isFillingScreen();
elevation = fill ? 0 :
(mWindowHasFocus ? DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP :
@@ -347,13 +343,13 @@ public class NonClientDecorView extends LinearLayout
releaseResources();
return;
}
if (mFrameRendererThread != null) {
if (mBackdropFrameRenderer != null) {
return;
}
final ThreadedRenderer renderer =
(ThreadedRenderer) mOwner.getDecorView().getHardwareRenderer();
if (renderer != null) {
mFrameRendererThread = new ResizeFrameThread(renderer, initialBounds);
mBackdropFrameRenderer = new BackdropFrameRenderer(this, renderer, initialBounds);
// Get rid of the shadow while we are resizing. Shadow drawing takes considerable time.
// If we want to get the shadow shown while resizing, we would need to elevate a new
// element which owns the caption and has the elevation.
@@ -363,16 +359,16 @@ public class NonClientDecorView extends LinearLayout
@Override
public boolean onContentDrawn(int xOffset, int yOffset, int xSize, int ySize) {
if (mFrameRendererThread == null) {
if (mBackdropFrameRenderer == null) {
return false;
}
return mFrameRendererThread.onContentDrawn(xOffset, yOffset, xSize, ySize);
return mBackdropFrameRenderer.onContentDrawn(xOffset, yOffset, xSize, ySize);
}
@Override
public void onRequestDraw(boolean reportNextDraw) {
if (mFrameRendererThread != null) {
mFrameRendererThread.onRequestDraw(reportNextDraw);
if (mBackdropFrameRenderer != null) {
mBackdropFrameRenderer.onRequestDraw(reportNextDraw);
} else if (reportNextDraw) {
// If render thread is gone, just report immediately.
if (isAttachedToWindow()) {
@@ -388,8 +384,8 @@ public class NonClientDecorView extends LinearLayout
@Override
public void onWindowSizeIsChanging(Rect newBounds) {
if (mFrameRendererThread != null) {
mFrameRendererThread.setTargetRect(newBounds);
if (mBackdropFrameRenderer != null) {
mBackdropFrameRenderer.setTargetRect(newBounds);
}
}
@@ -397,9 +393,9 @@ public class NonClientDecorView extends LinearLayout
* Release the renderer thread which is usually done when the user stops resizing.
*/
private void releaseThreadedRenderer() {
if (mFrameRendererThread != null) {
mFrameRendererThread.releaseRenderer();
mFrameRendererThread = null;
if (mBackdropFrameRenderer != null) {
mBackdropFrameRenderer.releaseRenderer();
mBackdropFrameRenderer = null;
// Bring the shadow back.
updateElevation();
}
@@ -413,256 +409,4 @@ public class NonClientDecorView extends LinearLayout
releaseThreadedRenderer();
}
/**
* The thread which draws the chrome while we are resizing.
* It starts with the creation and it ends once someone calls destroy().
* Any size changes can be passed by a call to setTargetRect will passed to the thread and
* executed via the Choreographer.
* TODO(b/24810450): Separate functionality from non-client-decor so that it can be used
* independently.
*/
private class ResizeFrameThread extends Thread implements Choreographer.FrameCallback {
// This is containing the last requested size by a resize command. Note that this size might
// or might not have been applied to the output already.
private final Rect mTargetRect = new Rect();
// The render nodes for the multi threaded renderer.
private ThreadedRenderer mRenderer;
private RenderNode mFrameAndBackdropNode;
private final Rect mOldTargetRect = new Rect();
private final Rect mNewTargetRect = new Rect();
private Choreographer mChoreographer;
// Cached size values from the last render for the case that the view hierarchy is gone
// during a configuration change.
private int mLastContentWidth;
private int mLastContentHeight;
private int mLastCaptionHeight;
private int mLastXOffset;
private int mLastYOffset;
// Whether to report when next frame is drawn or not.
private boolean mReportNextDraw;
ResizeFrameThread(ThreadedRenderer renderer, Rect initialBounds) {
setName("ResizeFrame");
mRenderer = renderer;
// Create a render node for the content and frame backdrop
// which can be resized independently from the content.
mFrameAndBackdropNode = RenderNode.create("FrameAndBackdropNode", null);
mRenderer.addRenderNode(mFrameAndBackdropNode, true);
// Set the initial bounds and draw once so that we do not get a broken frame.
mTargetRect.set(initialBounds);
synchronized (this) {
changeWindowSizeLocked(initialBounds);
}
// Kick off our draw thread.
start();
}
/**
* Call this function asynchronously when the window size has been changed. The change will
* be picked up once per frame and the frame will be re-rendered accordingly.
* @param newTargetBounds The new target bounds.
*/
public void setTargetRect(Rect newTargetBounds) {
synchronized (this) {
mTargetRect.set(newTargetBounds);
// Notify of a bounds change.
pingRenderLocked();
}
}
/**
* The window got replaced due to a configuration change.
*/
public void onConfigurationChange() {
synchronized (this) {
if (mRenderer != null) {
// Enforce a window redraw.
mOldTargetRect.set(0, 0, 0, 0);
pingRenderLocked();
}
}
}
/**
* All resources of the renderer will be released. This function can be called from the
* the UI thread as well as the renderer thread.
*/
public void releaseRenderer() {
synchronized (this) {
if (mRenderer != null) {
// Invalidate the current content bounds.
mRenderer.setContentDrawBounds(0, 0, 0, 0);
// Remove the render node again
// (see comment above - better to do that only once).
mRenderer.removeRenderNode(mFrameAndBackdropNode);
mRenderer = null;
// Exit the renderer loop.
pingRenderLocked();
}
}
}
@Override
public void run() {
try {
Looper.prepare();
synchronized (this) {
mChoreographer = Choreographer.getInstance();
// Draw at least once.
mChoreographer.postFrameCallback(this);
}
Looper.loop();
} finally {
releaseRenderer();
}
synchronized (this) {
// Make sure no more messages are being sent.
mChoreographer = null;
}
}
/**
* The implementation of the FrameCallback.
* @param frameTimeNanos The time in nanoseconds when the frame started being rendered,
* in the {@link System#nanoTime()} timebase. Divide this value by {@code 1000000}
*/
@Override
public void doFrame(long frameTimeNanos) {
synchronized (this) {
if (mRenderer == null) {
reportDrawIfNeeded();
// Tell the looper to stop. We are done.
Looper.myLooper().quit();
return;
}
mNewTargetRect.set(mTargetRect);
if (!mNewTargetRect.equals(mOldTargetRect) || mReportNextDraw) {
mOldTargetRect.set(mNewTargetRect);
changeWindowSizeLocked(mNewTargetRect);
}
}
}
/**
* The content is about to be drawn and we got the location of where it will be shown.
* If a "changeWindowSizeLocked" call has already been processed, we will re-issue the call
* if the previous call was ignored since the size was unknown.
* @param xOffset The x offset where the content is drawn to.
* @param yOffset The y offset where the content is drawn to.
* @param xSize The width size of the content. This should not be 0.
* @param ySize The height of the content.
* @return true if a frame should be requested after the content is drawn; false otherwise.
*/
public boolean onContentDrawn(int xOffset, int yOffset, int xSize, int ySize) {
synchronized (this) {
final boolean firstCall = mLastContentWidth == 0;
// The current content buffer is drawn here.
mLastContentWidth = xSize;
mLastContentHeight = ySize - mLastCaptionHeight;
mLastXOffset = xOffset;
mLastYOffset = yOffset;
mRenderer.setContentDrawBounds(
mLastXOffset,
mLastYOffset,
mLastXOffset + mLastContentWidth,
mLastYOffset + mLastCaptionHeight + mLastContentHeight);
// If this was the first call and changeWindowSizeLocked got already called prior
// to us, we should re-issue a changeWindowSizeLocked now.
return firstCall && (mLastCaptionHeight != 0 || !mShowDecor);
}
}
public void onRequestDraw(boolean reportNextDraw) {
synchronized (this) {
mReportNextDraw = reportNextDraw;
mOldTargetRect.set(0, 0, 0, 0);
pingRenderLocked();
}
}
/**
* Resizing the frame to fit the new window size.
* @param newBounds The window bounds which needs to be drawn.
*/
private void changeWindowSizeLocked(Rect newBounds) {
// While a configuration change is taking place the view hierarchy might become
// inaccessible. For that case we remember the previous metrics to avoid flashes.
// Note that even when there is no visible caption, the caption child will exist.
View caption = getChildAt(0);
if (caption != null) {
final int captionHeight = caption.getHeight();
// The caption height will probably never dynamically change while we are resizing.
// Once set to something other then 0 it should be kept that way.
if (captionHeight != 0) {
// Remember the height of the caption.
mLastCaptionHeight = captionHeight;
}
}
// Make sure that the other thread has already prepared the render draw calls for the
// content. If any size is 0, we have to wait for it to be drawn first.
if ((mLastCaptionHeight == 0 && mShowDecor) ||
mLastContentWidth == 0 || mLastContentHeight == 0) {
return;
}
// Since the surface is spanning the entire screen, we have to add the start offset of
// the bounds to get to the surface location.
final int left = mLastXOffset + newBounds.left;
final int top = mLastYOffset + newBounds.top;
final int width = newBounds.width();
final int height = newBounds.height();
mFrameAndBackdropNode.setLeftTopRightBottom(left, top, left + width, top + height);
// Draw the caption and content backdrops in to our render node.
DisplayListCanvas canvas = mFrameAndBackdropNode.start(width, height);
mCaptionBackgroundDrawable.setBounds(0, 0, left + width, top + mLastCaptionHeight);
mCaptionBackgroundDrawable.draw(canvas);
// The backdrop: clear everything with the background. Clipping is done elsewhere.
mResizingBackgroundDrawable.setBounds(0, mLastCaptionHeight, left + width, top + height);
mResizingBackgroundDrawable.draw(canvas);
mFrameAndBackdropNode.end(canvas);
// We need to render the node explicitly
mRenderer.drawRenderNode(mFrameAndBackdropNode);
reportDrawIfNeeded();
}
/**
* Notify view root that a frame has been drawn by us, if it has requested so.
*/
private void reportDrawIfNeeded() {
if (mReportNextDraw) {
if (isAttachedToWindow()) {
getViewRootImpl().reportDrawFinish();
}
mReportNextDraw = false;
}
}
/**
* Sends a message to the renderer to wake up and perform the next action which can be
* either the next rendering or the self destruction if mRenderer is null.
* Note: This call must be synchronized.
*/
private void pingRenderLocked() {
if (mChoreographer != null) {
mChoreographer.postFrameCallback(this);
}
}
}
}