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 if (mRenderer == null) { 210 // This can happen if 'releaseRenderer' is called immediately after 'start'. 211 return; 212 } 213 mChoreographer = Choreographer.getInstance(); 214 } 215 Looper.loop(); 216 } finally { 217 releaseRenderer(); 218 } 219 synchronized (this) { 220 // Make sure no more messages are being sent. 221 mChoreographer = null; 222 Choreographer.releaseInstance(); 223 } 224 } 225 226 /** 227 * The implementation of the FrameCallback. 228 * @param frameTimeNanos The time in nanoseconds when the frame started being rendered, 229 * in the {@link System#nanoTime()} timebase. Divide this value by {@code 1000000} 230 */ 231 @Override doFrame(long frameTimeNanos)232 public void doFrame(long frameTimeNanos) { 233 synchronized (this) { 234 if (mRenderer == null) { 235 reportDrawIfNeeded(); 236 // Tell the looper to stop. We are done. 237 Looper.myLooper().quit(); 238 return; 239 } 240 doFrameUncheckedLocked(); 241 } 242 } 243 doFrameUncheckedLocked()244 private void doFrameUncheckedLocked() { 245 mNewTargetRect.set(mTargetRect); 246 if (!mNewTargetRect.equals(mOldTargetRect) 247 || mOldFullscreen != mFullscreen 248 || !mSystemBarInsets.equals(mOldSystemBarInsets) 249 || mReportNextDraw) { 250 mOldFullscreen = mFullscreen; 251 mOldTargetRect.set(mNewTargetRect); 252 mOldSystemBarInsets.set(mSystemBarInsets); 253 redrawLocked(mNewTargetRect, mFullscreen); 254 } 255 } 256 257 /** 258 * The content is about to be drawn and we got the location of where it will be shown. 259 * If a "redrawLocked" call has already been processed, we will re-issue the call 260 * if the previous call was ignored since the size was unknown. 261 * @param xOffset The x offset where the content is drawn to. 262 * @param yOffset The y offset where the content is drawn to. 263 * @param xSize The width size of the content. This should not be 0. 264 * @param ySize The height of the content. 265 * @return true if a frame should be requested after the content is drawn; false otherwise. 266 */ onContentDrawn(int xOffset, int yOffset, int xSize, int ySize)267 boolean onContentDrawn(int xOffset, int yOffset, int xSize, int ySize) { 268 synchronized (this) { 269 final boolean firstCall = mLastContentWidth == 0; 270 // The current content buffer is drawn here. 271 mLastContentWidth = xSize; 272 mLastContentHeight = ySize - mLastCaptionHeight; 273 mLastXOffset = xOffset; 274 mLastYOffset = yOffset; 275 276 // Inform the renderer of the content's new bounds 277 mRenderer.setContentDrawBounds( 278 mLastXOffset, 279 mLastYOffset, 280 mLastXOffset + mLastContentWidth, 281 mLastYOffset + mLastCaptionHeight + mLastContentHeight); 282 283 // If this was the first call and redrawLocked got already called prior 284 // to us, we should re-issue a redrawLocked now. 285 return firstCall 286 && (mLastCaptionHeight != 0 || !mDecorView.isShowingCaption()); 287 } 288 } 289 onRequestDraw(boolean reportNextDraw)290 void onRequestDraw(boolean reportNextDraw) { 291 synchronized (this) { 292 mReportNextDraw = reportNextDraw; 293 mOldTargetRect.set(0, 0, 0, 0); 294 pingRenderLocked(true /* drawImmediate */); 295 } 296 } 297 298 /** 299 * Redraws the background, the caption and the system inset backgrounds if something changed. 300 * 301 * @param newBounds The window bounds which needs to be drawn. 302 * @param fullscreen Whether the window is currently drawing in fullscreen. 303 */ redrawLocked(Rect newBounds, boolean fullscreen)304 private void redrawLocked(Rect newBounds, boolean fullscreen) { 305 306 // While a configuration change is taking place the view hierarchy might become 307 // inaccessible. For that case we remember the previous metrics to avoid flashes. 308 // Note that even when there is no visible caption, the caption child will exist. 309 final int captionHeight = mDecorView.getCaptionHeight(); 310 311 // The caption height will probably never dynamically change while we are resizing. 312 // Once set to something other then 0 it should be kept that way. 313 if (captionHeight != 0) { 314 // Remember the height of the caption. 315 mLastCaptionHeight = captionHeight; 316 } 317 318 // Make sure that the other thread has already prepared the render draw calls for the 319 // content. If any size is 0, we have to wait for it to be drawn first. 320 if ((mLastCaptionHeight == 0 && mDecorView.isShowingCaption()) || 321 mLastContentWidth == 0 || mLastContentHeight == 0) { 322 return; 323 } 324 325 // Content may not be drawn at the surface origin, so we want to keep the offset when we're 326 // resizing it. 327 final int left = mLastXOffset + newBounds.left; 328 final int top = mLastYOffset + newBounds.top; 329 final int width = newBounds.width(); 330 final int height = newBounds.height(); 331 332 mFrameAndBackdropNode.setLeftTopRightBottom(left, top, left + width, top + height); 333 334 // Draw the caption and content backdrops in to our render node. 335 RecordingCanvas canvas = mFrameAndBackdropNode.beginRecording(width, height); 336 final Drawable drawable = mUserCaptionBackgroundDrawable != null 337 ? mUserCaptionBackgroundDrawable : mCaptionBackgroundDrawable; 338 339 if (drawable != null) { 340 drawable.setBounds(0, 0, left + width, top + mLastCaptionHeight); 341 drawable.draw(canvas); 342 } 343 344 // The backdrop: clear everything with the background. Clipping is done elsewhere. 345 if (mResizingBackgroundDrawable != null) { 346 mResizingBackgroundDrawable.setBounds(0, mLastCaptionHeight, left + width, top + height); 347 mResizingBackgroundDrawable.draw(canvas); 348 } 349 mFrameAndBackdropNode.endRecording(); 350 351 drawColorViews(left, top, width, height, fullscreen); 352 353 // We need to render the node explicitly 354 mRenderer.drawRenderNode(mFrameAndBackdropNode); 355 356 reportDrawIfNeeded(); 357 } 358 drawColorViews(int left, int top, int width, int height, boolean fullscreen)359 private void drawColorViews(int left, int top, int width, int height, boolean fullscreen) { 360 if (mSystemBarBackgroundNode == null) { 361 return; 362 } 363 RecordingCanvas canvas = mSystemBarBackgroundNode.beginRecording(width, height); 364 mSystemBarBackgroundNode.setLeftTopRightBottom(left, top, left + width, top + height); 365 final int topInset = mSystemBarInsets.top; 366 if (mStatusBarColor != null) { 367 mStatusBarColor.setBounds(0, 0, left + width, topInset); 368 mStatusBarColor.draw(canvas); 369 } 370 371 // We only want to draw the navigation bar if our window is currently fullscreen because we 372 // don't want the navigation bar background be moving around when resizing in docked mode. 373 // However, we need it for the transitions into/out of docked mode. 374 if (mNavigationBarColor != null && fullscreen) { 375 DecorView.getNavigationBarRect(width, height, mSystemBarInsets, mTmpRect, 1f); 376 mNavigationBarColor.setBounds(mTmpRect); 377 mNavigationBarColor.draw(canvas); 378 } 379 mSystemBarBackgroundNode.endRecording(); 380 mRenderer.drawRenderNode(mSystemBarBackgroundNode); 381 } 382 383 /** Notify view root that a frame has been drawn by us, if it has requested so. */ reportDrawIfNeeded()384 private void reportDrawIfNeeded() { 385 if (mReportNextDraw) { 386 if (mDecorView.isAttachedToWindow()) { 387 mDecorView.getViewRootImpl().reportDrawFinish(); 388 } 389 mReportNextDraw = false; 390 } 391 } 392 393 /** 394 * Sends a message to the renderer to wake up and perform the next action which can be 395 * either the next rendering or the self destruction if mRenderer is null. 396 * Note: This call must be synchronized. 397 * 398 * @param drawImmediate if we should draw immediately instead of scheduling a frame 399 */ pingRenderLocked(boolean drawImmediate)400 private void pingRenderLocked(boolean drawImmediate) { 401 if (mChoreographer != null && !drawImmediate) { 402 mChoreographer.postFrameCallback(this); 403 } else { 404 doFrameUncheckedLocked(); 405 } 406 } 407 setUserCaptionBackgroundDrawable(Drawable userCaptionBackgroundDrawable)408 void setUserCaptionBackgroundDrawable(Drawable userCaptionBackgroundDrawable) { 409 synchronized (this) { 410 mUserCaptionBackgroundDrawable = userCaptionBackgroundDrawable; 411 } 412 } 413 } 414