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 17 package com.android.internal.policy; 18 19 import android.graphics.Insets; 20 import android.graphics.RecordingCanvas; 21 import android.graphics.Rect; 22 import android.graphics.RenderNode; 23 import android.graphics.drawable.ColorDrawable; 24 import android.graphics.drawable.Drawable; 25 import android.os.Looper; 26 import android.view.Choreographer; 27 import android.view.ThreadedRenderer; 28 29 /** 30 * The thread which draws a fill in background while the app is resizing in areas where the app 31 * content draw is lagging behind the resize operation. 32 * It starts with the creation and it ends once someone calls destroy(). 33 * Any size changes can be passed by a call to setTargetRect will passed to the thread and 34 * executed via the Choreographer. 35 * @hide 36 */ 37 public class BackdropFrameRenderer extends Thread implements Choreographer.FrameCallback { 38 39 private DecorView mDecorView; 40 41 // This is containing the last requested size by a resize command. Note that this size might 42 // or might not have been applied to the output already. 43 private final Rect mTargetRect = new Rect(); 44 45 // The render nodes for the multi threaded renderer. 46 private ThreadedRenderer mRenderer; 47 private RenderNode mFrameAndBackdropNode; 48 private RenderNode mSystemBarBackgroundNode; 49 50 private final Rect mOldTargetRect = new Rect(); 51 private final Rect mNewTargetRect = new Rect(); 52 53 private Choreographer mChoreographer; 54 55 // Cached size values from the last render for the case that the view hierarchy is gone 56 // during a configuration change. 57 private int mLastContentWidth; 58 private int mLastContentHeight; 59 private int mLastCaptionHeight; 60 private int mLastXOffset; 61 private int mLastYOffset; 62 63 // Whether to report when next frame is drawn or not. 64 private boolean mReportNextDraw; 65 66 private Drawable mCaptionBackgroundDrawable; 67 private Drawable mUserCaptionBackgroundDrawable; 68 private Drawable mResizingBackgroundDrawable; 69 private ColorDrawable mStatusBarColor; 70 private ColorDrawable mNavigationBarColor; 71 private boolean mOldFullscreen; 72 private boolean mFullscreen; 73 private final Rect mOldSystemBarInsets = new Rect(); 74 private final Rect mSystemBarInsets = new Rect(); 75 private final Rect mTmpRect = new Rect(); 76 BackdropFrameRenderer(DecorView decorView, ThreadedRenderer renderer, Rect initialBounds, Drawable resizingBackgroundDrawable, Drawable captionBackgroundDrawable, Drawable userCaptionBackgroundDrawable, int statusBarColor, int navigationBarColor, boolean fullscreen, Insets systemBarInsets)77 public BackdropFrameRenderer(DecorView decorView, ThreadedRenderer renderer, Rect initialBounds, 78 Drawable resizingBackgroundDrawable, Drawable captionBackgroundDrawable, 79 Drawable userCaptionBackgroundDrawable, int statusBarColor, int navigationBarColor, 80 boolean fullscreen, Insets systemBarInsets) { 81 setName("ResizeFrame"); 82 83 mRenderer = renderer; 84 onResourcesLoaded(decorView, resizingBackgroundDrawable, captionBackgroundDrawable, 85 userCaptionBackgroundDrawable, statusBarColor, navigationBarColor); 86 87 // Create a render node for the content and frame backdrop 88 // which can be resized independently from the content. 89 mFrameAndBackdropNode = RenderNode.create("FrameAndBackdropNode", null); 90 91 mRenderer.addRenderNode(mFrameAndBackdropNode, true); 92 93 // Set the initial bounds and draw once so that we do not get a broken frame. 94 mTargetRect.set(initialBounds); 95 mFullscreen = fullscreen; 96 mOldFullscreen = fullscreen; 97 mSystemBarInsets.set(systemBarInsets.toRect()); 98 mOldSystemBarInsets.set(systemBarInsets.toRect()); 99 100 // Kick off our draw thread. 101 start(); 102 } 103 onResourcesLoaded(DecorView decorView, Drawable resizingBackgroundDrawable, Drawable captionBackgroundDrawableDrawable, Drawable userCaptionBackgroundDrawable, int statusBarColor, int navigationBarColor)104 void onResourcesLoaded(DecorView decorView, Drawable resizingBackgroundDrawable, 105 Drawable captionBackgroundDrawableDrawable, Drawable userCaptionBackgroundDrawable, 106 int statusBarColor, int navigationBarColor) { 107 synchronized (this) { 108 mDecorView = decorView; 109 mResizingBackgroundDrawable = resizingBackgroundDrawable != null 110 && resizingBackgroundDrawable.getConstantState() != null 111 ? resizingBackgroundDrawable.getConstantState().newDrawable() 112 : null; 113 mCaptionBackgroundDrawable = captionBackgroundDrawableDrawable != null 114 && captionBackgroundDrawableDrawable.getConstantState() != null 115 ? captionBackgroundDrawableDrawable.getConstantState().newDrawable() 116 : null; 117 mUserCaptionBackgroundDrawable = userCaptionBackgroundDrawable != null 118 && userCaptionBackgroundDrawable.getConstantState() != null 119 ? userCaptionBackgroundDrawable.getConstantState().newDrawable() 120 : null; 121 if (mCaptionBackgroundDrawable == null) { 122 mCaptionBackgroundDrawable = mResizingBackgroundDrawable; 123 } 124 if (statusBarColor != 0) { 125 mStatusBarColor = new ColorDrawable(statusBarColor); 126 addSystemBarNodeIfNeeded(); 127 } else { 128 mStatusBarColor = null; 129 } 130 if (navigationBarColor != 0) { 131 mNavigationBarColor = new ColorDrawable(navigationBarColor); 132 addSystemBarNodeIfNeeded(); 133 } else { 134 mNavigationBarColor = null; 135 } 136 } 137 } 138 addSystemBarNodeIfNeeded()139 private void addSystemBarNodeIfNeeded() { 140 if (mSystemBarBackgroundNode != null) { 141 return; 142 } 143 mSystemBarBackgroundNode = RenderNode.create("SystemBarBackgroundNode", null); 144 mRenderer.addRenderNode(mSystemBarBackgroundNode, false); 145 } 146 147 /** 148 * Call this function asynchronously when the window size has been changed or when the insets 149 * have changed or whether window switched between a fullscreen or non-fullscreen layout. 150 * The change will be picked up once per frame and the frame will be re-rendered accordingly. 151 * 152 * @param newTargetBounds The new target bounds. 153 * @param fullscreen Whether the window is currently drawing in fullscreen. 154 * @param systemBarInsets The current visible system insets for the window. 155 */ setTargetRect(Rect newTargetBounds, boolean fullscreen, Rect systemBarInsets)156 public void setTargetRect(Rect newTargetBounds, boolean fullscreen, Rect systemBarInsets) { 157 synchronized (this) { 158 mFullscreen = fullscreen; 159 mTargetRect.set(newTargetBounds); 160 mSystemBarInsets.set(systemBarInsets); 161 // Notify of a bounds change. 162 pingRenderLocked(false /* drawImmediate */); 163 } 164 } 165 166 /** 167 * The window got replaced due to a configuration change. 168 */ onConfigurationChange()169 public void onConfigurationChange() { 170 synchronized (this) { 171 if (mRenderer != null) { 172 // Enforce a window redraw. 173 mOldTargetRect.set(0, 0, 0, 0); 174 pingRenderLocked(false /* drawImmediate */); 175 } 176 } 177 } 178 179 /** 180 * All resources of the renderer will be released. This function can be called from the 181 * the UI thread as well as the renderer thread. 182 */ releaseRenderer()183 void releaseRenderer() { 184 synchronized (this) { 185 if (mRenderer != null) { 186 // Invalidate the current content bounds. 187 mRenderer.setContentDrawBounds(0, 0, 0, 0); 188 189 // Remove the render node again 190 // (see comment above - better to do that only once). 191 mRenderer.removeRenderNode(mFrameAndBackdropNode); 192 if (mSystemBarBackgroundNode != null) { 193 mRenderer.removeRenderNode(mSystemBarBackgroundNode); 194 } 195 196 mRenderer = null; 197 198 // Exit the renderer loop. 199 pingRenderLocked(false /* drawImmediate */); 200 } 201 } 202 } 203 204 @Override run()205 public void run() { 206 try { 207 Looper.prepare(); 208 synchronized (this) { 209 mChoreographer = Choreographer.getInstance(); 210 } 211 Looper.loop(); 212 } finally { 213 releaseRenderer(); 214 } 215 synchronized (this) { 216 // Make sure no more messages are being sent. 217 mChoreographer = null; 218 Choreographer.releaseInstance(); 219 } 220 } 221 222 /** 223 * The implementation of the FrameCallback. 224 * @param frameTimeNanos The time in nanoseconds when the frame started being rendered, 225 * in the {@link System#nanoTime()} timebase. Divide this value by {@code 1000000} 226 */ 227 @Override doFrame(long frameTimeNanos)228 public void doFrame(long frameTimeNanos) { 229 synchronized (this) { 230 if (mRenderer == null) { 231 reportDrawIfNeeded(); 232 // Tell the looper to stop. We are done. 233 Looper.myLooper().quit(); 234 return; 235 } 236 doFrameUncheckedLocked(); 237 } 238 } 239 doFrameUncheckedLocked()240 private void doFrameUncheckedLocked() { 241 mNewTargetRect.set(mTargetRect); 242 if (!mNewTargetRect.equals(mOldTargetRect) 243 || mOldFullscreen != mFullscreen 244 || !mSystemBarInsets.equals(mOldSystemBarInsets) 245 || mReportNextDraw) { 246 mOldFullscreen = mFullscreen; 247 mOldTargetRect.set(mNewTargetRect); 248 mOldSystemBarInsets.set(mSystemBarInsets); 249 redrawLocked(mNewTargetRect, mFullscreen); 250 } 251 } 252 253 /** 254 * The content is about to be drawn and we got the location of where it will be shown. 255 * If a "redrawLocked" call has already been processed, we will re-issue the call 256 * if the previous call was ignored since the size was unknown. 257 * @param xOffset The x offset where the content is drawn to. 258 * @param yOffset The y offset where the content is drawn to. 259 * @param xSize The width size of the content. This should not be 0. 260 * @param ySize The height of the content. 261 * @return true if a frame should be requested after the content is drawn; false otherwise. 262 */ onContentDrawn(int xOffset, int yOffset, int xSize, int ySize)263 boolean onContentDrawn(int xOffset, int yOffset, int xSize, int ySize) { 264 synchronized (this) { 265 final boolean firstCall = mLastContentWidth == 0; 266 // The current content buffer is drawn here. 267 mLastContentWidth = xSize; 268 mLastContentHeight = ySize - mLastCaptionHeight; 269 mLastXOffset = xOffset; 270 mLastYOffset = yOffset; 271 272 // Inform the renderer of the content's new bounds 273 mRenderer.setContentDrawBounds( 274 mLastXOffset, 275 mLastYOffset, 276 mLastXOffset + mLastContentWidth, 277 mLastYOffset + mLastCaptionHeight + mLastContentHeight); 278 279 // If this was the first call and redrawLocked got already called prior 280 // to us, we should re-issue a redrawLocked now. 281 return firstCall 282 && (mLastCaptionHeight != 0 || !mDecorView.isShowingCaption()); 283 } 284 } 285 onRequestDraw(boolean reportNextDraw)286 void onRequestDraw(boolean reportNextDraw) { 287 synchronized (this) { 288 mReportNextDraw = reportNextDraw; 289 mOldTargetRect.set(0, 0, 0, 0); 290 pingRenderLocked(true /* drawImmediate */); 291 } 292 } 293 294 /** 295 * Redraws the background, the caption and the system inset backgrounds if something changed. 296 * 297 * @param newBounds The window bounds which needs to be drawn. 298 * @param fullscreen Whether the window is currently drawing in fullscreen. 299 */ redrawLocked(Rect newBounds, boolean fullscreen)300 private void redrawLocked(Rect newBounds, boolean fullscreen) { 301 302 // While a configuration change is taking place the view hierarchy might become 303 // inaccessible. For that case we remember the previous metrics to avoid flashes. 304 // Note that even when there is no visible caption, the caption child will exist. 305 final int captionHeight = mDecorView.getCaptionHeight(); 306 307 // The caption height will probably never dynamically change while we are resizing. 308 // Once set to something other then 0 it should be kept that way. 309 if (captionHeight != 0) { 310 // Remember the height of the caption. 311 mLastCaptionHeight = captionHeight; 312 } 313 314 // Make sure that the other thread has already prepared the render draw calls for the 315 // content. If any size is 0, we have to wait for it to be drawn first. 316 if ((mLastCaptionHeight == 0 && mDecorView.isShowingCaption()) || 317 mLastContentWidth == 0 || mLastContentHeight == 0) { 318 return; 319 } 320 321 // Content may not be drawn at the surface origin, so we want to keep the offset when we're 322 // resizing it. 323 final int left = mLastXOffset + newBounds.left; 324 final int top = mLastYOffset + newBounds.top; 325 final int width = newBounds.width(); 326 final int height = newBounds.height(); 327 328 mFrameAndBackdropNode.setLeftTopRightBottom(left, top, left + width, top + height); 329 330 // Draw the caption and content backdrops in to our render node. 331 RecordingCanvas canvas = mFrameAndBackdropNode.beginRecording(width, height); 332 final Drawable drawable = mUserCaptionBackgroundDrawable != null 333 ? mUserCaptionBackgroundDrawable : mCaptionBackgroundDrawable; 334 335 if (drawable != null) { 336 drawable.setBounds(0, 0, left + width, top + mLastCaptionHeight); 337 drawable.draw(canvas); 338 } 339 340 // The backdrop: clear everything with the background. Clipping is done elsewhere. 341 if (mResizingBackgroundDrawable != null) { 342 mResizingBackgroundDrawable.setBounds(0, mLastCaptionHeight, left + width, top + height); 343 mResizingBackgroundDrawable.draw(canvas); 344 } 345 mFrameAndBackdropNode.endRecording(); 346 347 drawColorViews(left, top, width, height, fullscreen); 348 349 // We need to render the node explicitly 350 mRenderer.drawRenderNode(mFrameAndBackdropNode); 351 352 reportDrawIfNeeded(); 353 } 354 drawColorViews(int left, int top, int width, int height, boolean fullscreen)355 private void drawColorViews(int left, int top, int width, int height, boolean fullscreen) { 356 if (mSystemBarBackgroundNode == null) { 357 return; 358 } 359 RecordingCanvas canvas = mSystemBarBackgroundNode.beginRecording(width, height); 360 mSystemBarBackgroundNode.setLeftTopRightBottom(left, top, left + width, top + height); 361 final int topInset = mSystemBarInsets.top; 362 if (mStatusBarColor != null) { 363 mStatusBarColor.setBounds(0, 0, left + width, topInset); 364 mStatusBarColor.draw(canvas); 365 } 366 367 // We only want to draw the navigation bar if our window is currently fullscreen because we 368 // don't want the navigation bar background be moving around when resizing in docked mode. 369 // However, we need it for the transitions into/out of docked mode. 370 if (mNavigationBarColor != null && fullscreen) { 371 DecorView.getNavigationBarRect(width, height, mSystemBarInsets, mTmpRect, 1f); 372 mNavigationBarColor.setBounds(mTmpRect); 373 mNavigationBarColor.draw(canvas); 374 } 375 mSystemBarBackgroundNode.endRecording(); 376 mRenderer.drawRenderNode(mSystemBarBackgroundNode); 377 } 378 379 /** Notify view root that a frame has been drawn by us, if it has requested so. */ reportDrawIfNeeded()380 private void reportDrawIfNeeded() { 381 if (mReportNextDraw) { 382 if (mDecorView.isAttachedToWindow()) { 383 mDecorView.getViewRootImpl().reportDrawFinish(); 384 } 385 mReportNextDraw = false; 386 } 387 } 388 389 /** 390 * Sends a message to the renderer to wake up and perform the next action which can be 391 * either the next rendering or the self destruction if mRenderer is null. 392 * Note: This call must be synchronized. 393 * 394 * @param drawImmediate if we should draw immediately instead of scheduling a frame 395 */ pingRenderLocked(boolean drawImmediate)396 private void pingRenderLocked(boolean drawImmediate) { 397 if (mChoreographer != null && !drawImmediate) { 398 mChoreographer.postFrameCallback(this); 399 } else { 400 doFrameUncheckedLocked(); 401 } 402 } 403 setUserCaptionBackgroundDrawable(Drawable userCaptionBackgroundDrawable)404 void setUserCaptionBackgroundDrawable(Drawable userCaptionBackgroundDrawable) { 405 synchronized (this) { 406 mUserCaptionBackgroundDrawable = userCaptionBackgroundDrawable; 407 } 408 } 409 } 410