BackdropFrameRenderer.java revision 0b3562db3e01abce88f20bf2faeba61cce00d438
1/* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17package com.android.internal.policy; 18 19import com.android.internal.widget.NonClientDecorView; 20 21import android.graphics.Rect; 22import android.graphics.drawable.Drawable; 23import android.os.Looper; 24import android.view.Choreographer; 25import android.view.DisplayListCanvas; 26import android.view.RenderNode; 27import android.view.ThreadedRenderer; 28import android.view.View; 29 30/** 31 * The thread which draws a fill in background while the app is resizing in areas where the app 32 * content draw is lagging behind the resize operation. 33 * It starts with the creation and it ends once someone calls destroy(). 34 * Any size changes can be passed by a call to setTargetRect will passed to the thread and 35 * executed via the Choreographer. 36 * @hide 37 */ 38public class BackdropFrameRenderer extends Thread implements Choreographer.FrameCallback { 39 40 private NonClientDecorView mNonClientDecorView; 41 42 // This is containing the last requested size by a resize command. Note that this size might 43 // or might not have been applied to the output already. 44 private final Rect mTargetRect = new Rect(); 45 46 // The render nodes for the multi threaded renderer. 47 private ThreadedRenderer mRenderer; 48 private RenderNode mFrameAndBackdropNode; 49 50 private final Rect mOldTargetRect = new Rect(); 51 private final Rect mNewTargetRect = new Rect(); 52 private Choreographer mChoreographer; 53 54 // Cached size values from the last render for the case that the view hierarchy is gone 55 // during a configuration change. 56 private int mLastContentWidth; 57 private int mLastContentHeight; 58 private int mLastCaptionHeight; 59 private int mLastXOffset; 60 private int mLastYOffset; 61 62 // Whether to report when next frame is drawn or not. 63 private boolean mReportNextDraw; 64 65 public BackdropFrameRenderer(NonClientDecorView nonClientDecorView, 66 ThreadedRenderer renderer, 67 Rect initialBounds) { 68 mNonClientDecorView = nonClientDecorView; 69 setName("ResizeFrame"); 70 mRenderer = renderer; 71 72 // Create a render node for the content and frame backdrop 73 // which can be resized independently from the content. 74 mFrameAndBackdropNode = RenderNode.create("FrameAndBackdropNode", null); 75 76 mRenderer.addRenderNode(mFrameAndBackdropNode, true); 77 78 // Set the initial bounds and draw once so that we do not get a broken frame. 79 mTargetRect.set(initialBounds); 80 synchronized (this) { 81 changeWindowSizeLocked(initialBounds); 82 } 83 84 // Kick off our draw thread. 85 start(); 86 } 87 88 /** 89 * Call this function asynchronously when the window size has been changed. The change will 90 * be picked up once per frame and the frame will be re-rendered accordingly. 91 * @param newTargetBounds The new target bounds. 92 */ 93 public void setTargetRect(Rect newTargetBounds) { 94 synchronized (this) { 95 mTargetRect.set(newTargetBounds); 96 // Notify of a bounds change. 97 pingRenderLocked(); 98 } 99 } 100 101 /** 102 * The window got replaced due to a configuration change. 103 */ 104 public void onConfigurationChange() { 105 synchronized (this) { 106 if (mRenderer != null) { 107 // Enforce a window redraw. 108 mOldTargetRect.set(0, 0, 0, 0); 109 pingRenderLocked(); 110 } 111 } 112 } 113 114 /** 115 * All resources of the renderer will be released. This function can be called from the 116 * the UI thread as well as the renderer thread. 117 */ 118 public void releaseRenderer() { 119 synchronized (this) { 120 if (mRenderer != null) { 121 // Invalidate the current content bounds. 122 mRenderer.setContentDrawBounds(0, 0, 0, 0); 123 124 // Remove the render node again 125 // (see comment above - better to do that only once). 126 mRenderer.removeRenderNode(mFrameAndBackdropNode); 127 128 mRenderer = null; 129 130 // Exit the renderer loop. 131 pingRenderLocked(); 132 } 133 } 134 } 135 136 @Override 137 public void run() { 138 try { 139 Looper.prepare(); 140 synchronized (this) { 141 mChoreographer = Choreographer.getInstance(); 142 143 // Draw at least once. 144 mChoreographer.postFrameCallback(this); 145 } 146 Looper.loop(); 147 } finally { 148 releaseRenderer(); 149 } 150 synchronized (this) { 151 // Make sure no more messages are being sent. 152 mChoreographer = null; 153 } 154 } 155 156 /** 157 * The implementation of the FrameCallback. 158 * @param frameTimeNanos The time in nanoseconds when the frame started being rendered, 159 * in the {@link System#nanoTime()} timebase. Divide this value by {@code 1000000} 160 */ 161 @Override 162 public void doFrame(long frameTimeNanos) { 163 synchronized (this) { 164 if (mRenderer == null) { 165 reportDrawIfNeeded(); 166 // Tell the looper to stop. We are done. 167 Looper.myLooper().quit(); 168 return; 169 } 170 mNewTargetRect.set(mTargetRect); 171 if (!mNewTargetRect.equals(mOldTargetRect) || mReportNextDraw) { 172 mOldTargetRect.set(mNewTargetRect); 173 changeWindowSizeLocked(mNewTargetRect); 174 } 175 } 176 } 177 178 /** 179 * The content is about to be drawn and we got the location of where it will be shown. 180 * If a "changeWindowSizeLocked" call has already been processed, we will re-issue the call 181 * if the previous call was ignored since the size was unknown. 182 * @param xOffset The x offset where the content is drawn to. 183 * @param yOffset The y offset where the content is drawn to. 184 * @param xSize The width size of the content. This should not be 0. 185 * @param ySize The height of the content. 186 * @return true if a frame should be requested after the content is drawn; false otherwise. 187 */ 188 public boolean onContentDrawn(int xOffset, int yOffset, int xSize, int ySize) { 189 synchronized (this) { 190 final boolean firstCall = mLastContentWidth == 0; 191 // The current content buffer is drawn here. 192 mLastContentWidth = xSize; 193 mLastContentHeight = ySize - mLastCaptionHeight; 194 mLastXOffset = xOffset; 195 mLastYOffset = yOffset; 196 197 mRenderer.setContentDrawBounds( 198 mLastXOffset, 199 mLastYOffset, 200 mLastXOffset + mLastContentWidth, 201 mLastYOffset + mLastCaptionHeight + mLastContentHeight); 202 // If this was the first call and changeWindowSizeLocked got already called prior 203 // to us, we should re-issue a changeWindowSizeLocked now. 204 return firstCall && (mLastCaptionHeight != 0 || !mNonClientDecorView.mShowDecor); 205 } 206 } 207 208 public void onRequestDraw(boolean reportNextDraw) { 209 synchronized (this) { 210 mReportNextDraw = reportNextDraw; 211 mOldTargetRect.set(0, 0, 0, 0); 212 pingRenderLocked(); 213 } 214 } 215 216 /** 217 * Resizing the frame to fit the new window size. 218 * @param newBounds The window bounds which needs to be drawn. 219 */ 220 private void changeWindowSizeLocked(Rect newBounds) { 221 // While a configuration change is taking place the view hierarchy might become 222 // inaccessible. For that case we remember the previous metrics to avoid flashes. 223 // Note that even when there is no visible caption, the caption child will exist. 224 View caption = mNonClientDecorView.getChildAt(0); 225 if (caption != null) { 226 final int captionHeight = caption.getHeight(); 227 // The caption height will probably never dynamically change while we are resizing. 228 // Once set to something other then 0 it should be kept that way. 229 if (captionHeight != 0) { 230 // Remember the height of the caption. 231 mLastCaptionHeight = captionHeight; 232 } 233 } 234 // Make sure that the other thread has already prepared the render draw calls for the 235 // content. If any size is 0, we have to wait for it to be drawn first. 236 if ((mLastCaptionHeight == 0 && mNonClientDecorView.mShowDecor) || 237 mLastContentWidth == 0 || mLastContentHeight == 0) { 238 return; 239 } 240 // Since the surface is spanning the entire screen, we have to add the start offset of 241 // the bounds to get to the surface location. 242 final int left = mLastXOffset + newBounds.left; 243 final int top = mLastYOffset + newBounds.top; 244 final int width = newBounds.width(); 245 final int height = newBounds.height(); 246 247 mFrameAndBackdropNode.setLeftTopRightBottom(left, top, left + width, top + height); 248 249 // Draw the caption and content backdrops in to our render node. 250 DisplayListCanvas canvas = mFrameAndBackdropNode.start(width, height); 251 mNonClientDecorView.mCaptionBackgroundDrawable.setBounds( 252 0, 0, left + width, top + mLastCaptionHeight); 253 mNonClientDecorView.mCaptionBackgroundDrawable.draw(canvas); 254 255 // The backdrop: clear everything with the background. Clipping is done elsewhere. 256 mNonClientDecorView.mResizingBackgroundDrawable.setBounds( 257 0, mLastCaptionHeight, left + width, top + height); 258 mNonClientDecorView.mResizingBackgroundDrawable.draw(canvas); 259 mFrameAndBackdropNode.end(canvas); 260 261 // We need to render the node explicitly 262 mRenderer.drawRenderNode(mFrameAndBackdropNode); 263 264 reportDrawIfNeeded(); 265 } 266 267 /** 268 * Notify view root that a frame has been drawn by us, if it has requested so. 269 */ 270 private void reportDrawIfNeeded() { 271 if (mReportNextDraw) { 272 if (mNonClientDecorView.isAttachedToWindow()) { 273 mNonClientDecorView.getViewRootImpl().reportDrawFinish(); 274 } 275 mReportNextDraw = false; 276 } 277 } 278 279 /** 280 * Sends a message to the renderer to wake up and perform the next action which can be 281 * either the next rendering or the self destruction if mRenderer is null. 282 * Note: This call must be synchronized. 283 */ 284 private void pingRenderLocked() { 285 if (mChoreographer != null) { 286 mChoreographer.postFrameCallback(this); 287 } 288 } 289} 290