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