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.Rect; 20 import android.graphics.drawable.ColorDrawable; 21 import android.graphics.drawable.Drawable; 22 import android.os.Looper; 23 import android.view.Choreographer; 24 import android.view.DisplayListCanvas; 25 import android.view.RenderNode; 26 import android.view.ThreadedRenderer; 27 28 /** 29 * The thread which draws a fill in background while the app is resizing in areas where the app 30 * content draw is lagging behind the resize operation. 31 * It starts with the creation and it ends once someone calls destroy(). 32 * Any size changes can be passed by a call to setTargetRect will passed to the thread and 33 * executed via the Choreographer. 34 * @hide 35 */ 36 public class BackdropFrameRenderer extends Thread implements Choreographer.FrameCallback { 37 38 private DecorView mDecorView; 39 40 // This is containing the last requested size by a resize command. Note that this size might 41 // or might not have been applied to the output already. 42 private final Rect mTargetRect = new Rect(); 43 44 // The render nodes for the multi threaded renderer. 45 private ThreadedRenderer mRenderer; 46 private RenderNode mFrameAndBackdropNode; 47 private RenderNode mSystemBarBackgroundNode; 48 49 private final Rect mOldTargetRect = new Rect(); 50 private final Rect mNewTargetRect = new Rect(); 51 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 private Drawable mCaptionBackgroundDrawable; 66 private Drawable mUserCaptionBackgroundDrawable; 67 private Drawable mResizingBackgroundDrawable; 68 private ColorDrawable mStatusBarColor; 69 private ColorDrawable mNavigationBarColor; 70 private boolean mOldFullscreen; 71 private boolean mFullscreen; 72 private final int mResizeMode; 73 private final Rect mOldSystemInsets = new Rect(); 74 private final Rect mOldStableInsets = new Rect(); 75 private final Rect mSystemInsets = new Rect(); 76 private final Rect mStableInsets = new Rect(); 77 BackdropFrameRenderer(DecorView decorView, ThreadedRenderer renderer, Rect initialBounds, Drawable resizingBackgroundDrawable, Drawable captionBackgroundDrawable, Drawable userCaptionBackgroundDrawable, int statusBarColor, int navigationBarColor, boolean fullscreen, Rect systemInsets, Rect stableInsets, int resizeMode)78 public BackdropFrameRenderer(DecorView decorView, ThreadedRenderer renderer, Rect initialBounds, 79 Drawable resizingBackgroundDrawable, Drawable captionBackgroundDrawable, 80 Drawable userCaptionBackgroundDrawable, int statusBarColor, int navigationBarColor, 81 boolean fullscreen, Rect systemInsets, Rect stableInsets, int resizeMode) { 82 setName("ResizeFrame"); 83 84 mRenderer = renderer; 85 onResourcesLoaded(decorView, resizingBackgroundDrawable, captionBackgroundDrawable, 86 userCaptionBackgroundDrawable, statusBarColor, navigationBarColor); 87 88 // Create a render node for the content and frame backdrop 89 // which can be resized independently from the content. 90 mFrameAndBackdropNode = RenderNode.create("FrameAndBackdropNode", null); 91 92 mRenderer.addRenderNode(mFrameAndBackdropNode, true); 93 94 // Set the initial bounds and draw once so that we do not get a broken frame. 95 mTargetRect.set(initialBounds); 96 mFullscreen = fullscreen; 97 mOldFullscreen = fullscreen; 98 mSystemInsets.set(systemInsets); 99 mStableInsets.set(stableInsets); 100 mOldSystemInsets.set(systemInsets); 101 mOldStableInsets.set(stableInsets); 102 mResizeMode = resizeMode; 103 104 // Kick off our draw thread. 105 start(); 106 } 107 onResourcesLoaded(DecorView decorView, Drawable resizingBackgroundDrawable, Drawable captionBackgroundDrawableDrawable, Drawable userCaptionBackgroundDrawable, int statusBarColor, int navigationBarColor)108 void onResourcesLoaded(DecorView decorView, Drawable resizingBackgroundDrawable, 109 Drawable captionBackgroundDrawableDrawable, Drawable userCaptionBackgroundDrawable, 110 int statusBarColor, int navigationBarColor) { 111 mDecorView = decorView; 112 mResizingBackgroundDrawable = resizingBackgroundDrawable != null 113 ? resizingBackgroundDrawable.getConstantState().newDrawable() 114 : null; 115 mCaptionBackgroundDrawable = captionBackgroundDrawableDrawable != null 116 ? captionBackgroundDrawableDrawable.getConstantState().newDrawable() 117 : null; 118 mUserCaptionBackgroundDrawable = userCaptionBackgroundDrawable != 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 addSystemBarNodeIfNeeded()138 private void addSystemBarNodeIfNeeded() { 139 if (mSystemBarBackgroundNode != null) { 140 return; 141 } 142 mSystemBarBackgroundNode = RenderNode.create("SystemBarBackgroundNode", null); 143 mRenderer.addRenderNode(mSystemBarBackgroundNode, false); 144 } 145 146 /** 147 * Call this function asynchronously when the window size has been changed or when the insets 148 * have changed or whether window switched between a fullscreen or non-fullscreen layout. 149 * The change will be picked up once per frame and the frame will be re-rendered accordingly. 150 * 151 * @param newTargetBounds The new target bounds. 152 * @param fullscreen Whether the window is currently drawing in fullscreen. 153 * @param systemInsets The current visible system insets for the window. 154 * @param stableInsets The stable insets for the window. 155 */ setTargetRect(Rect newTargetBounds, boolean fullscreen, Rect systemInsets, Rect stableInsets)156 public void setTargetRect(Rect newTargetBounds, boolean fullscreen, Rect systemInsets, 157 Rect stableInsets) { 158 synchronized (this) { 159 mFullscreen = fullscreen; 160 mTargetRect.set(newTargetBounds); 161 mSystemInsets.set(systemInsets); 162 mStableInsets.set(stableInsets); 163 // Notify of a bounds change. 164 pingRenderLocked(false /* drawImmediate */); 165 } 166 } 167 168 /** 169 * The window got replaced due to a configuration change. 170 */ onConfigurationChange()171 public void onConfigurationChange() { 172 synchronized (this) { 173 if (mRenderer != null) { 174 // Enforce a window redraw. 175 mOldTargetRect.set(0, 0, 0, 0); 176 pingRenderLocked(false /* drawImmediate */); 177 } 178 } 179 } 180 181 /** 182 * All resources of the renderer will be released. This function can be called from the 183 * the UI thread as well as the renderer thread. 184 */ releaseRenderer()185 public void releaseRenderer() { 186 synchronized (this) { 187 if (mRenderer != null) { 188 // Invalidate the current content bounds. 189 mRenderer.setContentDrawBounds(0, 0, 0, 0); 190 191 // Remove the render node again 192 // (see comment above - better to do that only once). 193 mRenderer.removeRenderNode(mFrameAndBackdropNode); 194 if (mSystemBarBackgroundNode != null) { 195 mRenderer.removeRenderNode(mSystemBarBackgroundNode); 196 } 197 198 mRenderer = null; 199 200 // Exit the renderer loop. 201 pingRenderLocked(false /* drawImmediate */); 202 } 203 } 204 } 205 206 @Override run()207 public void run() { 208 try { 209 Looper.prepare(); 210 synchronized (this) { 211 mChoreographer = Choreographer.getInstance(); 212 } 213 Looper.loop(); 214 } finally { 215 releaseRenderer(); 216 } 217 synchronized (this) { 218 // Make sure no more messages are being sent. 219 mChoreographer = null; 220 Choreographer.releaseInstance(); 221 } 222 } 223 224 /** 225 * The implementation of the FrameCallback. 226 * @param frameTimeNanos The time in nanoseconds when the frame started being rendered, 227 * in the {@link System#nanoTime()} timebase. Divide this value by {@code 1000000} 228 */ 229 @Override doFrame(long frameTimeNanos)230 public void doFrame(long frameTimeNanos) { 231 synchronized (this) { 232 if (mRenderer == null) { 233 reportDrawIfNeeded(); 234 // Tell the looper to stop. We are done. 235 Looper.myLooper().quit(); 236 return; 237 } 238 doFrameUncheckedLocked(); 239 } 240 } 241 doFrameUncheckedLocked()242 private void doFrameUncheckedLocked() { 243 mNewTargetRect.set(mTargetRect); 244 if (!mNewTargetRect.equals(mOldTargetRect) 245 || mOldFullscreen != mFullscreen 246 || !mStableInsets.equals(mOldStableInsets) 247 || !mSystemInsets.equals(mOldSystemInsets) 248 || mReportNextDraw) { 249 mOldFullscreen = mFullscreen; 250 mOldTargetRect.set(mNewTargetRect); 251 mOldSystemInsets.set(mSystemInsets); 252 mOldStableInsets.set(mStableInsets); 253 redrawLocked(mNewTargetRect, mFullscreen, mSystemInsets, mStableInsets); 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 public 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 public 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 * @param systemInsets The current visible system insets for the window. 304 * @param stableInsets The stable insets for the window. 305 */ redrawLocked(Rect newBounds, boolean fullscreen, Rect systemInsets, Rect stableInsets)306 private void redrawLocked(Rect newBounds, boolean fullscreen, Rect systemInsets, 307 Rect stableInsets) { 308 309 // While a configuration change is taking place the view hierarchy might become 310 // inaccessible. For that case we remember the previous metrics to avoid flashes. 311 // Note that even when there is no visible caption, the caption child will exist. 312 final int captionHeight = mDecorView.getCaptionHeight(); 313 314 // The caption height will probably never dynamically change while we are resizing. 315 // Once set to something other then 0 it should be kept that way. 316 if (captionHeight != 0) { 317 // Remember the height of the caption. 318 mLastCaptionHeight = captionHeight; 319 } 320 321 // Make sure that the other thread has already prepared the render draw calls for the 322 // content. If any size is 0, we have to wait for it to be drawn first. 323 if ((mLastCaptionHeight == 0 && mDecorView.isShowingCaption()) || 324 mLastContentWidth == 0 || mLastContentHeight == 0) { 325 return; 326 } 327 328 // Since the surface is spanning the entire screen, we have to add the start offset of 329 // the bounds to get to the surface location. 330 final int left = mLastXOffset + newBounds.left; 331 final int top = mLastYOffset + newBounds.top; 332 final int width = newBounds.width(); 333 final int height = newBounds.height(); 334 335 mFrameAndBackdropNode.setLeftTopRightBottom(left, top, left + width, top + height); 336 337 // Draw the caption and content backdrops in to our render node. 338 DisplayListCanvas canvas = mFrameAndBackdropNode.start(width, height); 339 final Drawable drawable = mUserCaptionBackgroundDrawable != null 340 ? mUserCaptionBackgroundDrawable : mCaptionBackgroundDrawable; 341 342 if (drawable != null) { 343 drawable.setBounds(0, 0, left + width, top + mLastCaptionHeight); 344 drawable.draw(canvas); 345 } 346 347 // The backdrop: clear everything with the background. Clipping is done elsewhere. 348 if (mResizingBackgroundDrawable != null) { 349 mResizingBackgroundDrawable.setBounds(0, mLastCaptionHeight, left + width, top + height); 350 mResizingBackgroundDrawable.draw(canvas); 351 } 352 mFrameAndBackdropNode.end(canvas); 353 354 drawColorViews(left, top, width, height, fullscreen, systemInsets, stableInsets); 355 356 // We need to render the node explicitly 357 mRenderer.drawRenderNode(mFrameAndBackdropNode); 358 359 reportDrawIfNeeded(); 360 } 361 drawColorViews(int left, int top, int width, int height, boolean fullscreen, Rect systemInsets, Rect stableInsets)362 private void drawColorViews(int left, int top, int width, int height, 363 boolean fullscreen, Rect systemInsets, Rect stableInsets) { 364 if (mSystemBarBackgroundNode == null) { 365 return; 366 } 367 DisplayListCanvas canvas = mSystemBarBackgroundNode.start(width, height); 368 mSystemBarBackgroundNode.setLeftTopRightBottom(left, top, left + width, top + height); 369 final int topInset = DecorView.getColorViewTopInset(mStableInsets.top, mSystemInsets.top); 370 final int bottomInset = DecorView.getColorViewBottomInset(stableInsets.bottom, 371 systemInsets.bottom); 372 final int rightInset = DecorView.getColorViewRightInset(stableInsets.right, 373 systemInsets.right); 374 final int leftInset = DecorView.getColorViewLeftInset(stableInsets.left, 375 systemInsets.left); 376 if (mStatusBarColor != null) { 377 mStatusBarColor.setBounds(0, 0, left + width, topInset); 378 mStatusBarColor.draw(canvas); 379 } 380 381 // We only want to draw the navigation bar if our window is currently fullscreen because we 382 // don't want the navigation bar background be moving around when resizing in docked mode. 383 // However, we need it for the transitions into/out of docked mode. 384 if (mNavigationBarColor != null && fullscreen) { 385 final int size = DecorView.getNavBarSize(bottomInset, rightInset, leftInset); 386 if (DecorView.isNavBarToRightEdge(bottomInset, rightInset)) { 387 mNavigationBarColor.setBounds(width - size, 0, width, height); 388 } else if (DecorView.isNavBarToLeftEdge(bottomInset, leftInset)) { 389 mNavigationBarColor.setBounds(0, 0, size, height); 390 } else { 391 mNavigationBarColor.setBounds(0, height - size, width, height); 392 } 393 mNavigationBarColor.draw(canvas); 394 } 395 mSystemBarBackgroundNode.end(canvas); 396 mRenderer.drawRenderNode(mSystemBarBackgroundNode); 397 } 398 399 /** Notify view root that a frame has been drawn by us, if it has requested so. */ reportDrawIfNeeded()400 private void reportDrawIfNeeded() { 401 if (mReportNextDraw) { 402 if (mDecorView.isAttachedToWindow()) { 403 mDecorView.getViewRootImpl().reportDrawFinish(); 404 } 405 mReportNextDraw = false; 406 } 407 } 408 409 /** 410 * Sends a message to the renderer to wake up and perform the next action which can be 411 * either the next rendering or the self destruction if mRenderer is null. 412 * Note: This call must be synchronized. 413 * 414 * @param drawImmediate if we should draw immediately instead of scheduling a frame 415 */ pingRenderLocked(boolean drawImmediate)416 private void pingRenderLocked(boolean drawImmediate) { 417 if (mChoreographer != null && !drawImmediate) { 418 mChoreographer.postFrameCallback(this); 419 } else { 420 doFrameUncheckedLocked(); 421 } 422 } 423 setUserCaptionBackgroundDrawable(Drawable userCaptionBackgroundDrawable)424 void setUserCaptionBackgroundDrawable(Drawable userCaptionBackgroundDrawable) { 425 mUserCaptionBackgroundDrawable = userCaptionBackgroundDrawable; 426 } 427 } 428