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 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 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() != null 114 ? resizingBackgroundDrawable.getConstantState().newDrawable() 115 : null; 116 mCaptionBackgroundDrawable = captionBackgroundDrawableDrawable != null 117 && captionBackgroundDrawableDrawable.getConstantState() != null 118 ? captionBackgroundDrawableDrawable.getConstantState().newDrawable() 119 : null; 120 mUserCaptionBackgroundDrawable = userCaptionBackgroundDrawable != null 121 && userCaptionBackgroundDrawable.getConstantState() != null 122 ? userCaptionBackgroundDrawable.getConstantState().newDrawable() 123 : null; 124 if (mCaptionBackgroundDrawable == null) { 125 mCaptionBackgroundDrawable = mResizingBackgroundDrawable; 126 } 127 if (statusBarColor != 0) { 128 mStatusBarColor = new ColorDrawable(statusBarColor); 129 addSystemBarNodeIfNeeded(); 130 } else { 131 mStatusBarColor = null; 132 } 133 if (navigationBarColor != 0) { 134 mNavigationBarColor = new ColorDrawable(navigationBarColor); 135 addSystemBarNodeIfNeeded(); 136 } else { 137 mNavigationBarColor = null; 138 } 139 } 140 141 private void addSystemBarNodeIfNeeded() { 142 if (mSystemBarBackgroundNode != null) { 143 return; 144 } 145 mSystemBarBackgroundNode = RenderNode.create("SystemBarBackgroundNode", null); 146 mRenderer.addRenderNode(mSystemBarBackgroundNode, false); 147 } 148 149 /** 150 * Call this function asynchronously when the window size has been changed or when the insets 151 * have changed or whether window switched between a fullscreen or non-fullscreen layout. 152 * The change will be picked up once per frame and the frame will be re-rendered accordingly. 153 * 154 * @param newTargetBounds The new target bounds. 155 * @param fullscreen Whether the window is currently drawing in fullscreen. 156 * @param systemInsets The current visible system insets for the window. 157 * @param stableInsets The stable insets for the window. 158 */ 159 public void setTargetRect(Rect newTargetBounds, boolean fullscreen, Rect systemInsets, 160 Rect stableInsets) { 161 synchronized (this) { 162 mFullscreen = fullscreen; 163 mTargetRect.set(newTargetBounds); 164 mSystemInsets.set(systemInsets); 165 mStableInsets.set(stableInsets); 166 // Notify of a bounds change. 167 pingRenderLocked(false /* drawImmediate */); 168 } 169 } 170 171 /** 172 * The window got replaced due to a configuration change. 173 */ 174 public void onConfigurationChange() { 175 synchronized (this) { 176 if (mRenderer != null) { 177 // Enforce a window redraw. 178 mOldTargetRect.set(0, 0, 0, 0); 179 pingRenderLocked(false /* drawImmediate */); 180 } 181 } 182 } 183 184 /** 185 * All resources of the renderer will be released. This function can be called from the 186 * the UI thread as well as the renderer thread. 187 */ 188 public void releaseRenderer() { 189 synchronized (this) { 190 if (mRenderer != null) { 191 // Invalidate the current content bounds. 192 mRenderer.setContentDrawBounds(0, 0, 0, 0); 193 194 // Remove the render node again 195 // (see comment above - better to do that only once). 196 mRenderer.removeRenderNode(mFrameAndBackdropNode); 197 if (mSystemBarBackgroundNode != null) { 198 mRenderer.removeRenderNode(mSystemBarBackgroundNode); 199 } 200 201 mRenderer = null; 202 203 // Exit the renderer loop. 204 pingRenderLocked(false /* drawImmediate */); 205 } 206 } 207 } 208 209 @Override 210 public void run() { 211 try { 212 Looper.prepare(); 213 synchronized (this) { 214 mChoreographer = Choreographer.getInstance(); 215 } 216 Looper.loop(); 217 } finally { 218 releaseRenderer(); 219 } 220 synchronized (this) { 221 // Make sure no more messages are being sent. 222 mChoreographer = null; 223 Choreographer.releaseInstance(); 224 } 225 } 226 227 /** 228 * The implementation of the FrameCallback. 229 * @param frameTimeNanos The time in nanoseconds when the frame started being rendered, 230 * in the {@link System#nanoTime()} timebase. Divide this value by {@code 1000000} 231 */ 232 @Override 233 public void doFrame(long frameTimeNanos) { 234 synchronized (this) { 235 if (mRenderer == null) { 236 reportDrawIfNeeded(); 237 // Tell the looper to stop. We are done. 238 Looper.myLooper().quit(); 239 return; 240 } 241 doFrameUncheckedLocked(); 242 } 243 } 244 245 private void doFrameUncheckedLocked() { 246 mNewTargetRect.set(mTargetRect); 247 if (!mNewTargetRect.equals(mOldTargetRect) 248 || mOldFullscreen != mFullscreen 249 || !mStableInsets.equals(mOldStableInsets) 250 || !mSystemInsets.equals(mOldSystemInsets) 251 || mReportNextDraw) { 252 mOldFullscreen = mFullscreen; 253 mOldTargetRect.set(mNewTargetRect); 254 mOldSystemInsets.set(mSystemInsets); 255 mOldStableInsets.set(mStableInsets); 256 redrawLocked(mNewTargetRect, mFullscreen, mSystemInsets, mStableInsets); 257 } 258 } 259 260 /** 261 * The content is about to be drawn and we got the location of where it will be shown. 262 * If a "redrawLocked" call has already been processed, we will re-issue the call 263 * if the previous call was ignored since the size was unknown. 264 * @param xOffset The x offset where the content is drawn to. 265 * @param yOffset The y offset where the content is drawn to. 266 * @param xSize The width size of the content. This should not be 0. 267 * @param ySize The height of the content. 268 * @return true if a frame should be requested after the content is drawn; false otherwise. 269 */ 270 public boolean onContentDrawn(int xOffset, int yOffset, int xSize, int ySize) { 271 synchronized (this) { 272 final boolean firstCall = mLastContentWidth == 0; 273 // The current content buffer is drawn here. 274 mLastContentWidth = xSize; 275 mLastContentHeight = ySize - mLastCaptionHeight; 276 mLastXOffset = xOffset; 277 mLastYOffset = yOffset; 278 279 // Inform the renderer of the content's new bounds 280 mRenderer.setContentDrawBounds( 281 mLastXOffset, 282 mLastYOffset, 283 mLastXOffset + mLastContentWidth, 284 mLastYOffset + mLastCaptionHeight + mLastContentHeight); 285 286 // If this was the first call and redrawLocked got already called prior 287 // to us, we should re-issue a redrawLocked now. 288 return firstCall 289 && (mLastCaptionHeight != 0 || !mDecorView.isShowingCaption()); 290 } 291 } 292 293 public void onRequestDraw(boolean reportNextDraw) { 294 synchronized (this) { 295 mReportNextDraw = reportNextDraw; 296 mOldTargetRect.set(0, 0, 0, 0); 297 pingRenderLocked(true /* drawImmediate */); 298 } 299 } 300 301 /** 302 * Redraws the background, the caption and the system inset backgrounds if something changed. 303 * 304 * @param newBounds The window bounds which needs to be drawn. 305 * @param fullscreen Whether the window is currently drawing in fullscreen. 306 * @param systemInsets The current visible system insets for the window. 307 * @param stableInsets The stable insets for the window. 308 */ 309 private void redrawLocked(Rect newBounds, boolean fullscreen, Rect systemInsets, 310 Rect stableInsets) { 311 312 // While a configuration change is taking place the view hierarchy might become 313 // inaccessible. For that case we remember the previous metrics to avoid flashes. 314 // Note that even when there is no visible caption, the caption child will exist. 315 final int captionHeight = mDecorView.getCaptionHeight(); 316 317 // The caption height will probably never dynamically change while we are resizing. 318 // Once set to something other then 0 it should be kept that way. 319 if (captionHeight != 0) { 320 // Remember the height of the caption. 321 mLastCaptionHeight = captionHeight; 322 } 323 324 // Make sure that the other thread has already prepared the render draw calls for the 325 // content. If any size is 0, we have to wait for it to be drawn first. 326 if ((mLastCaptionHeight == 0 && mDecorView.isShowingCaption()) || 327 mLastContentWidth == 0 || mLastContentHeight == 0) { 328 return; 329 } 330 331 // Since the surface is spanning the entire screen, we have to add the start offset of 332 // the bounds to get to the surface location. 333 final int left = mLastXOffset + newBounds.left; 334 final int top = mLastYOffset + newBounds.top; 335 final int width = newBounds.width(); 336 final int height = newBounds.height(); 337 338 mFrameAndBackdropNode.setLeftTopRightBottom(left, top, left + width, top + height); 339 340 // Draw the caption and content backdrops in to our render node. 341 DisplayListCanvas canvas = mFrameAndBackdropNode.start(width, height); 342 final Drawable drawable = mUserCaptionBackgroundDrawable != null 343 ? mUserCaptionBackgroundDrawable : mCaptionBackgroundDrawable; 344 345 if (drawable != null) { 346 drawable.setBounds(0, 0, left + width, top + mLastCaptionHeight); 347 drawable.draw(canvas); 348 } 349 350 // The backdrop: clear everything with the background. Clipping is done elsewhere. 351 if (mResizingBackgroundDrawable != null) { 352 mResizingBackgroundDrawable.setBounds(0, mLastCaptionHeight, left + width, top + height); 353 mResizingBackgroundDrawable.draw(canvas); 354 } 355 mFrameAndBackdropNode.end(canvas); 356 357 drawColorViews(left, top, width, height, fullscreen, systemInsets, stableInsets); 358 359 // We need to render the node explicitly 360 mRenderer.drawRenderNode(mFrameAndBackdropNode); 361 362 reportDrawIfNeeded(); 363 } 364 365 private void drawColorViews(int left, int top, int width, int height, 366 boolean fullscreen, Rect systemInsets, Rect stableInsets) { 367 if (mSystemBarBackgroundNode == null) { 368 return; 369 } 370 DisplayListCanvas canvas = mSystemBarBackgroundNode.start(width, height); 371 mSystemBarBackgroundNode.setLeftTopRightBottom(left, top, left + width, top + height); 372 final int topInset = DecorView.getColorViewTopInset(mStableInsets.top, mSystemInsets.top); 373 final int bottomInset = DecorView.getColorViewBottomInset(stableInsets.bottom, 374 systemInsets.bottom); 375 final int rightInset = DecorView.getColorViewRightInset(stableInsets.right, 376 systemInsets.right); 377 final int leftInset = DecorView.getColorViewLeftInset(stableInsets.left, 378 systemInsets.left); 379 if (mStatusBarColor != null) { 380 mStatusBarColor.setBounds(0, 0, left + width, topInset); 381 mStatusBarColor.draw(canvas); 382 } 383 384 // We only want to draw the navigation bar if our window is currently fullscreen because we 385 // don't want the navigation bar background be moving around when resizing in docked mode. 386 // However, we need it for the transitions into/out of docked mode. 387 if (mNavigationBarColor != null && fullscreen) { 388 final int size = DecorView.getNavBarSize(bottomInset, rightInset, leftInset); 389 if (DecorView.isNavBarToRightEdge(bottomInset, rightInset)) { 390 mNavigationBarColor.setBounds(width - size, 0, width, height); 391 } else if (DecorView.isNavBarToLeftEdge(bottomInset, leftInset)) { 392 mNavigationBarColor.setBounds(0, 0, size, height); 393 } else { 394 mNavigationBarColor.setBounds(0, height - size, width, height); 395 } 396 mNavigationBarColor.draw(canvas); 397 } 398 mSystemBarBackgroundNode.end(canvas); 399 mRenderer.drawRenderNode(mSystemBarBackgroundNode); 400 } 401 402 /** Notify view root that a frame has been drawn by us, if it has requested so. */ 403 private void reportDrawIfNeeded() { 404 if (mReportNextDraw) { 405 if (mDecorView.isAttachedToWindow()) { 406 mDecorView.getViewRootImpl().reportDrawFinish(); 407 } 408 mReportNextDraw = false; 409 } 410 } 411 412 /** 413 * Sends a message to the renderer to wake up and perform the next action which can be 414 * either the next rendering or the self destruction if mRenderer is null. 415 * Note: This call must be synchronized. 416 * 417 * @param drawImmediate if we should draw immediately instead of scheduling a frame 418 */ 419 private void pingRenderLocked(boolean drawImmediate) { 420 if (mChoreographer != null && !drawImmediate) { 421 mChoreographer.postFrameCallback(this); 422 } else { 423 doFrameUncheckedLocked(); 424 } 425 } 426 427 void setUserCaptionBackgroundDrawable(Drawable userCaptionBackgroundDrawable) { 428 mUserCaptionBackgroundDrawable = userCaptionBackgroundDrawable; 429 } 430 } 431