399 lines
16 KiB
Java
399 lines
16 KiB
Java
/*
|
|
* 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 android.graphics.Insets;
|
|
import android.graphics.RecordingCanvas;
|
|
import android.graphics.Rect;
|
|
import android.graphics.RenderNode;
|
|
import android.graphics.drawable.ColorDrawable;
|
|
import android.graphics.drawable.Drawable;
|
|
import android.os.Looper;
|
|
import android.view.Choreographer;
|
|
import android.view.ThreadedRenderer;
|
|
|
|
/**
|
|
* 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 DecorView mDecorView;
|
|
|
|
// 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 RenderNode mSystemBarBackgroundNode;
|
|
|
|
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 mLastXOffset;
|
|
private int mLastYOffset;
|
|
|
|
// Whether to report when next frame is drawn or not.
|
|
private boolean mReportNextDraw;
|
|
|
|
private Drawable mCaptionBackgroundDrawable;
|
|
private Drawable mUserCaptionBackgroundDrawable;
|
|
private Drawable mResizingBackgroundDrawable;
|
|
private ColorDrawable mStatusBarColor;
|
|
private ColorDrawable mNavigationBarColor;
|
|
private boolean mOldFullscreen;
|
|
private boolean mFullscreen;
|
|
private final Rect mOldSystemBarInsets = new Rect();
|
|
private final Rect mSystemBarInsets = new Rect();
|
|
private final Rect mTmpRect = new Rect();
|
|
|
|
public BackdropFrameRenderer(DecorView decorView, ThreadedRenderer renderer, Rect initialBounds,
|
|
Drawable resizingBackgroundDrawable, Drawable captionBackgroundDrawable,
|
|
Drawable userCaptionBackgroundDrawable, int statusBarColor, int navigationBarColor,
|
|
boolean fullscreen, Insets systemBarInsets) {
|
|
setName("ResizeFrame");
|
|
|
|
mRenderer = renderer;
|
|
onResourcesLoaded(decorView, resizingBackgroundDrawable, captionBackgroundDrawable,
|
|
userCaptionBackgroundDrawable, statusBarColor, navigationBarColor);
|
|
|
|
// 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);
|
|
mFullscreen = fullscreen;
|
|
mOldFullscreen = fullscreen;
|
|
mSystemBarInsets.set(systemBarInsets.toRect());
|
|
mOldSystemBarInsets.set(systemBarInsets.toRect());
|
|
|
|
// Kick off our draw thread.
|
|
start();
|
|
}
|
|
|
|
void onResourcesLoaded(DecorView decorView, Drawable resizingBackgroundDrawable,
|
|
Drawable captionBackgroundDrawableDrawable, Drawable userCaptionBackgroundDrawable,
|
|
int statusBarColor, int navigationBarColor) {
|
|
synchronized (this) {
|
|
mDecorView = decorView;
|
|
mResizingBackgroundDrawable = resizingBackgroundDrawable != null
|
|
&& resizingBackgroundDrawable.getConstantState() != null
|
|
? resizingBackgroundDrawable.getConstantState().newDrawable()
|
|
: null;
|
|
mCaptionBackgroundDrawable = captionBackgroundDrawableDrawable != null
|
|
&& captionBackgroundDrawableDrawable.getConstantState() != null
|
|
? captionBackgroundDrawableDrawable.getConstantState().newDrawable()
|
|
: null;
|
|
mUserCaptionBackgroundDrawable = userCaptionBackgroundDrawable != null
|
|
&& userCaptionBackgroundDrawable.getConstantState() != null
|
|
? userCaptionBackgroundDrawable.getConstantState().newDrawable()
|
|
: null;
|
|
if (mCaptionBackgroundDrawable == null) {
|
|
mCaptionBackgroundDrawable = mResizingBackgroundDrawable;
|
|
}
|
|
if (statusBarColor != 0) {
|
|
mStatusBarColor = new ColorDrawable(statusBarColor);
|
|
addSystemBarNodeIfNeeded();
|
|
} else {
|
|
mStatusBarColor = null;
|
|
}
|
|
if (navigationBarColor != 0) {
|
|
mNavigationBarColor = new ColorDrawable(navigationBarColor);
|
|
addSystemBarNodeIfNeeded();
|
|
} else {
|
|
mNavigationBarColor = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void addSystemBarNodeIfNeeded() {
|
|
if (mSystemBarBackgroundNode != null) {
|
|
return;
|
|
}
|
|
mSystemBarBackgroundNode = RenderNode.create("SystemBarBackgroundNode", null);
|
|
mRenderer.addRenderNode(mSystemBarBackgroundNode, false);
|
|
}
|
|
|
|
/**
|
|
* Call this function asynchronously when the window size has been changed or when the insets
|
|
* have changed or whether window switched between a fullscreen or non-fullscreen layout.
|
|
* The change will be picked up once per frame and the frame will be re-rendered accordingly.
|
|
*
|
|
* @param newTargetBounds The new target bounds.
|
|
* @param fullscreen Whether the window is currently drawing in fullscreen.
|
|
* @param systemBarInsets The current visible system insets for the window.
|
|
*/
|
|
public void setTargetRect(Rect newTargetBounds, boolean fullscreen, Rect systemBarInsets) {
|
|
synchronized (this) {
|
|
mFullscreen = fullscreen;
|
|
mTargetRect.set(newTargetBounds);
|
|
mSystemBarInsets.set(systemBarInsets);
|
|
// Notify of a bounds change.
|
|
pingRenderLocked(false /* drawImmediate */);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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(false /* drawImmediate */);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* All resources of the renderer will be released. This function can be called from the
|
|
* the UI thread as well as the renderer thread.
|
|
*/
|
|
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);
|
|
if (mSystemBarBackgroundNode != null) {
|
|
mRenderer.removeRenderNode(mSystemBarBackgroundNode);
|
|
}
|
|
|
|
mRenderer = null;
|
|
|
|
// Exit the renderer loop.
|
|
pingRenderLocked(false /* drawImmediate */);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void run() {
|
|
try {
|
|
Looper.prepare();
|
|
synchronized (this) {
|
|
if (mRenderer == null) {
|
|
// This can happen if 'releaseRenderer' is called immediately after 'start'.
|
|
return;
|
|
}
|
|
mChoreographer = Choreographer.getInstance();
|
|
}
|
|
Looper.loop();
|
|
} finally {
|
|
releaseRenderer();
|
|
}
|
|
synchronized (this) {
|
|
// Make sure no more messages are being sent.
|
|
mChoreographer = null;
|
|
Choreographer.releaseInstance();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
doFrameUncheckedLocked();
|
|
}
|
|
}
|
|
|
|
private void doFrameUncheckedLocked() {
|
|
mNewTargetRect.set(mTargetRect);
|
|
if (!mNewTargetRect.equals(mOldTargetRect)
|
|
|| mOldFullscreen != mFullscreen
|
|
|| !mSystemBarInsets.equals(mOldSystemBarInsets)
|
|
|| mReportNextDraw) {
|
|
mOldFullscreen = mFullscreen;
|
|
mOldTargetRect.set(mNewTargetRect);
|
|
mOldSystemBarInsets.set(mSystemBarInsets);
|
|
redrawLocked(mNewTargetRect, mFullscreen);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The content is about to be drawn and we got the location of where it will be shown.
|
|
* If a "redrawLocked" 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.
|
|
*/
|
|
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;
|
|
mLastXOffset = xOffset;
|
|
mLastYOffset = yOffset;
|
|
|
|
// Inform the renderer of the content's new bounds
|
|
mRenderer.setContentDrawBounds(
|
|
mLastXOffset,
|
|
mLastYOffset,
|
|
mLastXOffset + mLastContentWidth,
|
|
mLastYOffset + mLastContentHeight);
|
|
|
|
// If this was the first call and redrawLocked got already called prior
|
|
// to us, we should re-issue a redrawLocked now.
|
|
return firstCall;
|
|
}
|
|
}
|
|
|
|
void onRequestDraw(boolean reportNextDraw) {
|
|
synchronized (this) {
|
|
mReportNextDraw = reportNextDraw;
|
|
mOldTargetRect.set(0, 0, 0, 0);
|
|
pingRenderLocked(true /* drawImmediate */);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Redraws the background, the caption and the system inset backgrounds if something changed.
|
|
*
|
|
* @param newBounds The window bounds which needs to be drawn.
|
|
* @param fullscreen Whether the window is currently drawing in fullscreen.
|
|
*/
|
|
private void redrawLocked(Rect newBounds, boolean fullscreen) {
|
|
|
|
// 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 (mLastContentWidth == 0 || mLastContentHeight == 0) {
|
|
return;
|
|
}
|
|
|
|
// Content may not be drawn at the surface origin, so we want to keep the offset when we're
|
|
// resizing it.
|
|
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.
|
|
RecordingCanvas canvas = mFrameAndBackdropNode.beginRecording(width, height);
|
|
final Drawable drawable = mUserCaptionBackgroundDrawable != null
|
|
? mUserCaptionBackgroundDrawable : mCaptionBackgroundDrawable;
|
|
|
|
if (drawable != null) {
|
|
drawable.setBounds(0, 0, left + width, top);
|
|
drawable.draw(canvas);
|
|
}
|
|
|
|
// The backdrop: clear everything with the background. Clipping is done elsewhere.
|
|
if (mResizingBackgroundDrawable != null) {
|
|
mResizingBackgroundDrawable.setBounds(0, 0, left + width, top + height);
|
|
mResizingBackgroundDrawable.draw(canvas);
|
|
}
|
|
mFrameAndBackdropNode.endRecording();
|
|
|
|
drawColorViews(left, top, width, height, fullscreen);
|
|
|
|
// We need to render the node explicitly
|
|
mRenderer.drawRenderNode(mFrameAndBackdropNode);
|
|
|
|
reportDrawIfNeeded();
|
|
}
|
|
|
|
private void drawColorViews(int left, int top, int width, int height, boolean fullscreen) {
|
|
if (mSystemBarBackgroundNode == null) {
|
|
return;
|
|
}
|
|
RecordingCanvas canvas = mSystemBarBackgroundNode.beginRecording(width, height);
|
|
mSystemBarBackgroundNode.setLeftTopRightBottom(left, top, left + width, top + height);
|
|
final int topInset = mSystemBarInsets.top;
|
|
if (mStatusBarColor != null) {
|
|
mStatusBarColor.setBounds(0, 0, left + width, topInset);
|
|
mStatusBarColor.draw(canvas);
|
|
}
|
|
|
|
// We only want to draw the navigation bar if our window is currently fullscreen because we
|
|
// don't want the navigation bar background be moving around when resizing in docked mode.
|
|
// However, we need it for the transitions into/out of docked mode.
|
|
if (mNavigationBarColor != null && fullscreen) {
|
|
DecorView.getNavigationBarRect(width, height, mSystemBarInsets, mTmpRect, 1f);
|
|
mNavigationBarColor.setBounds(mTmpRect);
|
|
mNavigationBarColor.draw(canvas);
|
|
}
|
|
mSystemBarBackgroundNode.endRecording();
|
|
mRenderer.drawRenderNode(mSystemBarBackgroundNode);
|
|
}
|
|
|
|
/** Notify view root that a frame has been drawn by us, if it has requested so. */
|
|
private void reportDrawIfNeeded() {
|
|
if (mReportNextDraw) {
|
|
if (mDecorView.isAttachedToWindow()) {
|
|
mDecorView.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.
|
|
*
|
|
* @param drawImmediate if we should draw immediately instead of scheduling a frame
|
|
*/
|
|
private void pingRenderLocked(boolean drawImmediate) {
|
|
if (mChoreographer != null && !drawImmediate) {
|
|
mChoreographer.postFrameCallback(this);
|
|
} else {
|
|
doFrameUncheckedLocked();
|
|
}
|
|
}
|
|
|
|
void setUserCaptionBackgroundDrawable(Drawable userCaptionBackgroundDrawable) {
|
|
synchronized (this) {
|
|
mUserCaptionBackgroundDrawable = userCaptionBackgroundDrawable;
|
|
}
|
|
}
|
|
}
|