diff options
| author | 2015-11-17 11:26:56 -0800 | |
|---|---|---|
| committer | 2015-11-17 12:19:45 -0800 | |
| commit | 0b3562db3e01abce88f20bf2faeba61cce00d438 (patch) | |
| tree | a4e38aeb7844fe87b5433dfd3e5e01a3ff5dd5e7 | |
| parent | 0d7e912b001838748bfb61b92b81d2f493ef4a6b (diff) | |
Moved BackdropFrameRenderer from NonClientDecorView to its own class file
Bug: 24810450
Change-Id: Id86e97733161499bbc59617433792f5ddc4e7f9e
| -rw-r--r-- | core/java/com/android/internal/policy/BackdropFrameRenderer.java | 289 | ||||
| -rw-r--r-- | core/java/com/android/internal/widget/NonClientDecorView.java | 294 |
2 files changed, 308 insertions, 275 deletions
diff --git a/core/java/com/android/internal/policy/BackdropFrameRenderer.java b/core/java/com/android/internal/policy/BackdropFrameRenderer.java new file mode 100644 index 000000000000..1eeb36310086 --- /dev/null +++ b/core/java/com/android/internal/policy/BackdropFrameRenderer.java @@ -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); + } + } +} diff --git a/core/java/com/android/internal/widget/NonClientDecorView.java b/core/java/com/android/internal/widget/NonClientDecorView.java index 6ae1b983042f..48420dae2265 100644 --- a/core/java/com/android/internal/widget/NonClientDecorView.java +++ b/core/java/com/android/internal/widget/NonClientDecorView.java @@ -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); - } - } - } } |