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 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().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 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 */ 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 */ 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 */ 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 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 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 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 */ 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 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 */ 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 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 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 final int size = DecorView.getNavBarSize(bottomInset, rightInset); 384 if (DecorView.isNavBarToRightEdge(bottomInset, rightInset)) { 385 mNavigationBarColor.setBounds(width - size, 0, width, height); 386 } else { 387 mNavigationBarColor.setBounds(0, height - size, width, height); 388 } 389 mNavigationBarColor.draw(canvas); 390 } 391 mSystemBarBackgroundNode.end(canvas); 392 mRenderer.drawRenderNode(mSystemBarBackgroundNode); 393 } 394 395 /** Notify view root that a frame has been drawn by us, if it has requested so. */ 396 private void reportDrawIfNeeded() { 397 if (mReportNextDraw) { 398 if (mDecorView.isAttachedToWindow()) { 399 mDecorView.getViewRootImpl().reportDrawFinish(); 400 } 401 mReportNextDraw = false; 402 } 403 } 404 405 /** 406 * Sends a message to the renderer to wake up and perform the next action which can be 407 * either the next rendering or the self destruction if mRenderer is null. 408 * Note: This call must be synchronized. 409 * 410 * @param drawImmediate if we should draw immediately instead of scheduling a frame 411 */ 412 private void pingRenderLocked(boolean drawImmediate) { 413 if (mChoreographer != null && !drawImmediate) { 414 mChoreographer.postFrameCallback(this); 415 } else { 416 doFrameUncheckedLocked(); 417 } 418 } 419 420 void setUserCaptionBackgroundDrawable(Drawable userCaptionBackgroundDrawable) { 421 mUserCaptionBackgroundDrawable = userCaptionBackgroundDrawable; 422 } 423} 424